@xuda.io/runtime-bundle
Version:
The Xuda Runtime Bundle refers to a collection of scripts and libraries packaged together to provide the necessary runtime environment for executing plugins or components in the Xuda platform.
30,562 lines • 1.19 MB
JavaScript
/*! jQuery v3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween | (c) OpenJS Foundation and other contributors | jquery.org/license */
!function (e, t) { "use strict"; "object" == typeof module && "object" == typeof module.exports ? module.exports = e.document ? t(e, !0) : function (e) { if (!e.document) throw new Error("jQuery requires a window with a document"); return t(e) } : t(e) }("undefined" != typeof window ? window : this, function (ie, e) { "use strict"; var oe = [], r = Object.getPrototypeOf, ae = oe.slice, g = oe.flat ? function (e) { return oe.flat.call(e) } : function (e) { return oe.concat.apply([], e) }, s = oe.push, se = oe.indexOf, n = {}, i = n.toString, ue = n.hasOwnProperty, o = ue.toString, a = o.call(Object), le = {}, v = function (e) { return "function" == typeof e && "number" != typeof e.nodeType && "function" != typeof e.item }, y = function (e) { return null != e && e === e.window }, m = ie.document, u = { type: !0, src: !0, nonce: !0, noModule: !0 }; function b(e, t, n) { var r, i, o = (n = n || m).createElement("script"); if (o.text = e, t) for (r in u) (i = t[r] || t.getAttribute && t.getAttribute(r)) && o.setAttribute(r, i); n.head.appendChild(o).parentNode.removeChild(o) } function x(e) { return null == e ? e + "" : "object" == typeof e || "function" == typeof e ? n[i.call(e)] || "object" : typeof e } var t = "3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween", l = /HTML$/i, ce = function (e, t) { return new ce.fn.init(e, t) }; function c(e) { var t = !!e && "length" in e && e.length, n = x(e); return !v(e) && !y(e) && ("array" === n || 0 === t || "number" == typeof t && 0 < t && t - 1 in e) } function fe(e, t) { return e.nodeName && e.nodeName.toLowerCase() === t.toLowerCase() } ce.fn = ce.prototype = { jquery: t, constructor: ce, length: 0, toArray: function () { return ae.call(this) }, get: function (e) { return null == e ? ae.call(this) : e < 0 ? this[e + this.length] : this[e] }, pushStack: function (e) { var t = ce.merge(this.constructor(), e); return t.prevObject = this, t }, each: function (e) { return ce.each(this, e) }, map: function (n) { return this.pushStack(ce.map(this, function (e, t) { return n.call(e, t, e) })) }, slice: function () { return this.pushStack(ae.apply(this, arguments)) }, first: function () { return this.eq(0) }, last: function () { return this.eq(-1) }, even: function () { return this.pushStack(ce.grep(this, function (e, t) { return (t + 1) % 2 })) }, odd: function () { return this.pushStack(ce.grep(this, function (e, t) { return t % 2 })) }, eq: function (e) { var t = this.length, n = +e + (e < 0 ? t : 0); return this.pushStack(0 <= n && n < t ? [this[n]] : []) }, end: function () { return this.prevObject || this.constructor() }, push: s, sort: oe.sort, splice: oe.splice }, ce.extend = ce.fn.extend = function () { var e, t, n, r, i, o, a = arguments[0] || {}, s = 1, u = arguments.length, l = !1; for ("boolean" == typeof a && (l = a, a = arguments[s] || {}, s++), "object" == typeof a || v(a) || (a = {}), s === u && (a = this, s--); s < u; s++)if (null != (e = arguments[s])) for (t in e) r = e[t], "__proto__" !== t && a !== r && (l && r && (ce.isPlainObject(r) || (i = Array.isArray(r))) ? (n = a[t], o = i && !Array.isArray(n) ? [] : i || ce.isPlainObject(n) ? n : {}, i = !1, a[t] = ce.extend(l, o, r)) : void 0 !== r && (a[t] = r)); return a }, ce.extend({ expando: "jQuery" + (t + Math.random()).replace(/\D/g, ""), isReady: !0, error: function (e) { throw new Error(e) }, noop: function () { }, isPlainObject: function (e) { var t, n; return !(!e || "[object Object]" !== i.call(e)) && (!(t = r(e)) || "function" == typeof (n = ue.call(t, "constructor") && t.constructor) && o.call(n) === a) }, isEmptyObject: function (e) { var t; for (t in e) return !1; return !0 }, globalEval: function (e, t, n) { b(e, { nonce: t && t.nonce }, n) }, each: function (e, t) { var n, r = 0; if (c(e)) { for (n = e.length; r < n; r++)if (!1 === t.call(e[r], r, e[r])) break } else for (r in e) if (!1 === t.call(e[r], r, e[r])) break; return e }, text: function (e) { var t, n = "", r = 0, i = e.nodeType; if (!i) while (t = e[r++]) n += ce.text(t); return 1 === i || 11 === i ? e.textContent : 9 === i ? e.documentElement.textContent : 3 === i || 4 === i ? e.nodeValue : n }, makeArray: function (e, t) { var n = t || []; return null != e && (c(Object(e)) ? ce.merge(n, "string" == typeof e ? [e] : e) : s.call(n, e)), n }, inArray: function (e, t, n) { return null == t ? -1 : se.call(t, e, n) }, isXMLDoc: function (e) { var t = e && e.namespaceURI, n = e && (e.ownerDocument || e).documentElement; return !l.test(t || n && n.nodeName || "HTML") }, merge: function (e, t) { for (var n = +t.length, r = 0, i = e.length; r < n; r++)e[i++] = t[r]; return e.length = i, e }, grep: function (e, t, n) { for (var r = [], i = 0, o = e.length, a = !n; i < o; i++)!t(e[i], i) !== a && r.push(e[i]); return r }, map: function (e, t, n) { var r, i, o = 0, a = []; if (c(e)) for (r = e.length; o < r; o++)null != (i = t(e[o], o, n)) && a.push(i); else for (o in e) null != (i = t(e[o], o, n)) && a.push(i); return g(a) }, guid: 1, support: le }), "function" == typeof Symbol && (ce.fn[Symbol.iterator] = oe[Symbol.iterator]), ce.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "), function (e, t) { n["[object " + t + "]"] = t.toLowerCase() }); var de = oe.pop, pe = oe.sort, he = oe.splice, ge = "[\\x20\\t\\r\\n\\f]", ve = new RegExp("^" + ge + "+|((?:^|[^\\\\])(?:\\\\.)*)" + ge + "+$", "g"); ce.contains = function (e, t) { var n = t && t.parentNode; return e === n || !(!n || 1 !== n.nodeType || !(e.contains ? e.contains(n) : e.compareDocumentPosition && 16 & e.compareDocumentPosition(n))) }; var f = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; function d(e, t) { return t ? "\0" === e ? "\ufffd" : e.slice(0, -1) + "\\" + e.charCodeAt(e.length - 1).toString(16) + " " : "\\" + e } ce.escapeSelector = function (e) { return (e + "").replace(f, d) }; var ye = m, me = s; !function () { var e, x, w, o, a, C, r, T, p, i, E = me, k = ce.expando, S = 0, n = 0, s = W(), c = W(), u = W(), h = W(), l = function (e, t) { return e === t && (a = !0), 0 }, f = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", t = "(?:\\\\[\\da-fA-F]{1,6}" + ge + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", d = "\\[" + ge + "*(" + t + ")(?:" + ge + "*([*^$|!~]?=)" + ge + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + t + "))|)" + ge + "*\\]", g = ":(" + t + ")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|" + d + ")*)|.*)\\)|)", v = new RegExp(ge + "+", "g"), y = new RegExp("^" + ge + "*," + ge + "*"), m = new RegExp("^" + ge + "*([>+~]|" + ge + ")" + ge + "*"), b = new RegExp(ge + "|>"), A = new RegExp(g), D = new RegExp("^" + t + "$"), N = { ID: new RegExp("^#(" + t + ")"), CLASS: new RegExp("^\\.(" + t + ")"), TAG: new RegExp("^(" + t + "|[*])"), ATTR: new RegExp("^" + d), PSEUDO: new RegExp("^" + g), CHILD: new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + ge + "*(even|odd|(([+-]|)(\\d*)n|)" + ge + "*(?:([+-]|)" + ge + "*(\\d+)|))" + ge + "*\\)|)", "i"), bool: new RegExp("^(?:" + f + ")$", "i"), needsContext: new RegExp("^" + ge + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + ge + "*((?:-\\d)?\\d*)" + ge + "*\\)|)(?=[^-]|$)", "i") }, L = /^(?:input|select|textarea|button)$/i, j = /^h\d$/i, O = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, P = /[+~]/, H = new RegExp("\\\\[\\da-fA-F]{1,6}" + ge + "?|\\\\([^\\r\\n\\f])", "g"), q = function (e, t) { var n = "0x" + e.slice(1) - 65536; return t || (n < 0 ? String.fromCharCode(n + 65536) : String.fromCharCode(n >> 10 | 55296, 1023 & n | 56320)) }, R = function () { V() }, M = K(function (e) { return !0 === e.disabled && fe(e, "fieldset") }, { dir: "parentNode", next: "legend" }); try { E.apply(oe = ae.call(ye.childNodes), ye.childNodes), oe[ye.childNodes.length].nodeType } catch (e) { E = { apply: function (e, t) { me.apply(e, ae.call(t)) }, call: function (e) { me.apply(e, ae.call(arguments, 1)) } } } function I(t, e, n, r) { var i, o, a, s, u, l, c, f = e && e.ownerDocument, d = e ? e.nodeType : 9; if (n = n || [], "string" != typeof t || !t || 1 !== d && 9 !== d && 11 !== d) return n; if (!r && (V(e), e = e || C, T)) { if (11 !== d && (u = O.exec(t))) if (i = u[1]) { if (9 === d) { if (!(a = e.getElementById(i))) return n; if (a.id === i) return E.call(n, a), n } else if (f && (a = f.getElementById(i)) && I.contains(e, a) && a.id === i) return E.call(n, a), n } else { if (u[2]) return E.apply(n, e.getElementsByTagName(t)), n; if ((i = u[3]) && e.getElementsByClassName) return E.apply(n, e.getElementsByClassName(i)), n } if (!(h[t + " "] || p && p.test(t))) { if (c = t, f = e, 1 === d && (b.test(t) || m.test(t))) { (f = P.test(t) && X(e.parentNode) || e) == e && le.scope || ((s = e.getAttribute("id")) ? s = ce.escapeSelector(s) : e.setAttribute("id", s = k)), o = (l = Y(t)).length; while (o--) l[o] = (s ? "#" + s : ":scope") + " " + G(l[o]); c = l.join(",") } try { return E.apply(n, f.querySelectorAll(c)), n } catch (e) { h(t, !0) } finally { s === k && e.removeAttribute("id") } } } return re(t.replace(ve, "$1"), e, n, r) } function W() { var r = []; return function e(t, n) { return r.push(t + " ") > x.cacheLength && delete e[r.shift()], e[t + " "] = n } } function B(e) { return e[k] = !0, e } function F(e) { var t = C.createElement("fieldset"); try { return !!e(t) } catch (e) { return !1 } finally { t.parentNode && t.parentNode.removeChild(t), t = null } } function $(t) { return function (e) { return fe(e, "input") && e.type === t } } function _(t) { return function (e) { return (fe(e, "input") || fe(e, "button")) && e.type === t } } function z(t) { return function (e) { return "form" in e ? e.parentNode && !1 === e.disabled ? "label" in e ? "label" in e.parentNode ? e.parentNode.disabled === t : e.disabled === t : e.isDisabled === t || e.isDisabled !== !t && M(e) === t : e.disabled === t : "label" in e && e.disabled === t } } function U(a) { return B(function (o) { return o = +o, B(function (e, t) { var n, r = a([], e.length, o), i = r.length; while (i--) e[n = r[i]] && (e[n] = !(t[n] = e[n])) }) }) } function X(e) { return e && "undefined" != typeof e.getElementsByTagName && e } function V(e) { var t, n = e ? e.ownerDocument || e : ye; return n != C && 9 === n.nodeType && n.documentElement && (r = (C = n).documentElement, T = !ce.isXMLDoc(C), i = r.matches || r.webkitMatchesSelector || r.msMatchesSelector, r.msMatchesSelector && ye != C && (t = C.defaultView) && t.top !== t && t.addEventListener("unload", R), le.getById = F(function (e) { return r.appendChild(e).id = ce.expando, !C.getElementsByName || !C.getElementsByName(ce.expando).length }), le.disconnectedMatch = F(function (e) { return i.call(e, "*") }), le.scope = F(function () { return C.querySelectorAll(":scope") }), le.cssHas = F(function () { try { return C.querySelector(":has(*,:jqfake)"), !1 } catch (e) { return !0 } }), le.getById ? (x.filter.ID = function (e) { var t = e.replace(H, q); return function (e) { return e.getAttribute("id") === t } }, x.find.ID = function (e, t) { if ("undefined" != typeof t.getElementById && T) { var n = t.getElementById(e); return n ? [n] : [] } }) : (x.filter.ID = function (e) { var n = e.replace(H, q); return function (e) { var t = "undefined" != typeof e.getAttributeNode && e.getAttributeNode("id"); return t && t.value === n } }, x.find.ID = function (e, t) { if ("undefined" != typeof t.getElementById && T) { var n, r, i, o = t.getElementById(e); if (o) { if ((n = o.getAttributeNode("id")) && n.value === e) return [o]; i = t.getElementsByName(e), r = 0; while (o = i[r++]) if ((n = o.getAttributeNode("id")) && n.value === e) return [o] } return [] } }), x.find.TAG = function (e, t) { return "undefined" != typeof t.getElementsByTagName ? t.getElementsByTagName(e) : t.querySelectorAll(e) }, x.find.CLASS = function (e, t) { if ("undefined" != typeof t.getElementsByClassName && T) return t.getElementsByClassName(e) }, p = [], F(function (e) { var t; r.appendChild(e).innerHTML = "<a id='" + k + "' href='' disabled='disabled'></a><select id='" + k + "-\r\\' disabled='disabled'><option selected=''></option></select>", e.querySelectorAll("[selected]").length || p.push("\\[" + ge + "*(?:value|" + f + ")"), e.querySelectorAll("[id~=" + k + "-]").length || p.push("~="), e.querySelectorAll("a#" + k + "+*").length || p.push(".#.+[+~]"), e.querySelectorAll(":checked").length || p.push(":checked"), (t = C.createElement("input")).setAttribute("type", "hidden"), e.appendChild(t).setAttribute("name", "D"), r.appendChild(e).disabled = !0, 2 !== e.querySelectorAll(":disabled").length && p.push(":enabled", ":disabled"), (t = C.createElement("input")).setAttribute("name", ""), e.appendChild(t), e.querySelectorAll("[name='']").length || p.push("\\[" + ge + "*name" + ge + "*=" + ge + "*(?:''|\"\")") }), le.cssHas || p.push(":has"), p = p.length && new RegExp(p.join("|")), l = function (e, t) { if (e === t) return a = !0, 0; var n = !e.compareDocumentPosition - !t.compareDocumentPosition; return n || (1 & (n = (e.ownerDocument || e) == (t.ownerDocument || t) ? e.compareDocumentPosition(t) : 1) || !le.sortDetached && t.compareDocumentPosition(e) === n ? e === C || e.ownerDocument == ye && I.contains(ye, e) ? -1 : t === C || t.ownerDocument == ye && I.contains(ye, t) ? 1 : o ? se.call(o, e) - se.call(o, t) : 0 : 4 & n ? -1 : 1) }), C } for (e in I.matches = function (e, t) { return I(e, null, null, t) }, I.matchesSelector = function (e, t) { if (V(e), T && !h[t + " "] && (!p || !p.test(t))) try { var n = i.call(e, t); if (n || le.disconnectedMatch || e.document && 11 !== e.document.nodeType) return n } catch (e) { h(t, !0) } return 0 < I(t, C, null, [e]).length }, I.contains = function (e, t) { return (e.ownerDocument || e) != C && V(e), ce.contains(e, t) }, I.attr = function (e, t) { (e.ownerDocument || e) != C && V(e); var n = x.attrHandle[t.toLowerCase()], r = n && ue.call(x.attrHandle, t.toLowerCase()) ? n(e, t, !T) : void 0; return void 0 !== r ? r : e.getAttribute(t) }, I.error = function (e) { throw new Error("Syntax error, unrecognized expression: " + e) }, ce.uniqueSort = function (e) { var t, n = [], r = 0, i = 0; if (a = !le.sortStable, o = !le.sortStable && ae.call(e, 0), pe.call(e, l), a) { while (t = e[i++]) t === e[i] && (r = n.push(i)); while (r--) he.call(e, n[r], 1) } return o = null, e }, ce.fn.uniqueSort = function () { return this.pushStack(ce.uniqueSort(ae.apply(this))) }, (x = ce.expr = { cacheLength: 50, createPseudo: B, match: N, attrHandle: {}, find: {}, relative: { ">": { dir: "parentNode", first: !0 }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: !0 }, "~": { dir: "previousSibling" } }, preFilter: { ATTR: function (e) { return e[1] = e[1].replace(H, q), e[3] = (e[3] || e[4] || e[5] || "").replace(H, q), "~=" === e[2] && (e[3] = " " + e[3] + " "), e.slice(0, 4) }, CHILD: function (e) { return e[1] = e[1].toLowerCase(), "nth" === e[1].slice(0, 3) ? (e[3] || I.error(e[0]), e[4] = +(e[4] ? e[5] + (e[6] || 1) : 2 * ("even" === e[3] || "odd" === e[3])), e[5] = +(e[7] + e[8] || "odd" === e[3])) : e[3] && I.error(e[0]), e }, PSEUDO: function (e) { var t, n = !e[6] && e[2]; return N.CHILD.test(e[0]) ? null : (e[3] ? e[2] = e[4] || e[5] || "" : n && A.test(n) && (t = Y(n, !0)) && (t = n.indexOf(")", n.length - t) - n.length) && (e[0] = e[0].slice(0, t), e[2] = n.slice(0, t)), e.slice(0, 3)) } }, filter: { TAG: function (e) { var t = e.replace(H, q).toLowerCase(); return "*" === e ? function () { return !0 } : function (e) { return fe(e, t) } }, CLASS: function (e) { var t = s[e + " "]; return t || (t = new RegExp("(^|" + ge + ")" + e + "(" + ge + "|$)")) && s(e, function (e) { return t.test("string" == typeof e.className && e.className || "undefined" != typeof e.getAttribute && e.getAttribute("class") || "") }) }, ATTR: function (n, r, i) { return function (e) { var t = I.attr(e, n); return null == t ? "!=" === r : !r || (t += "", "=" === r ? t === i : "!=" === r ? t !== i : "^=" === r ? i && 0 === t.indexOf(i) : "*=" === r ? i && -1 < t.indexOf(i) : "$=" === r ? i && t.slice(-i.length) === i : "~=" === r ? -1 < (" " + t.replace(v, " ") + " ").indexOf(i) : "|=" === r && (t === i || t.slice(0, i.length + 1) === i + "-")) } }, CHILD: function (p, e, t, h, g) { var v = "nth" !== p.slice(0, 3), y = "last" !== p.slice(-4), m = "of-type" === e; return 1 === h && 0 === g ? function (e) { return !!e.parentNode } : function (e, t, n) { var r, i, o, a, s, u = v !== y ? "nextSibling" : "previousSibling", l = e.parentNode, c = m && e.nodeName.toLowerCase(), f = !n && !m, d = !1; if (l) { if (v) { while (u) { o = e; while (o = o[u]) if (m ? fe(o, c) : 1 === o.nodeType) return !1; s = u = "only" === p && !s && "nextSibling" } return !0 } if (s = [y ? l.firstChild : l.lastChild], y && f) { d = (a = (r = (i = l[k] || (l[k] = {}))[p] || [])[0] === S && r[1]) && r[2], o = a && l.childNodes[a]; while (o = ++a && o && o[u] || (d = a = 0) || s.pop()) if (1 === o.nodeType && ++d && o === e) { i[p] = [S, a, d]; break } } else if (f && (d = a = (r = (i = e[k] || (e[k] = {}))[p] || [])[0] === S && r[1]), !1 === d) while (o = ++a && o && o[u] || (d = a = 0) || s.pop()) if ((m ? fe(o, c) : 1 === o.nodeType) && ++d && (f && ((i = o[k] || (o[k] = {}))[p] = [S, d]), o === e)) break; return (d -= g) === h || d % h == 0 && 0 <= d / h } } }, PSEUDO: function (e, o) { var t, a = x.pseudos[e] || x.setFilters[e.toLowerCase()] || I.error("unsupported pseudo: " + e); return a[k] ? a(o) : 1 < a.length ? (t = [e, e, "", o], x.setFilters.hasOwnProperty(e.toLowerCase()) ? B(function (e, t) { var n, r = a(e, o), i = r.length; while (i--) e[n = se.call(e, r[i])] = !(t[n] = r[i]) }) : function (e) { return a(e, 0, t) }) : a } }, pseudos: { not: B(function (e) { var r = [], i = [], s = ne(e.replace(ve, "$1")); return s[k] ? B(function (e, t, n, r) { var i, o = s(e, null, r, []), a = e.length; while (a--) (i = o[a]) && (e[a] = !(t[a] = i)) }) : function (e, t, n) { return r[0] = e, s(r, null, n, i), r[0] = null, !i.pop() } }), has: B(function (t) { return function (e) { return 0 < I(t, e).length } }), contains: B(function (t) { return t = t.replace(H, q), function (e) { return -1 < (e.textContent || ce.text(e)).indexOf(t) } }), lang: B(function (n) { return D.test(n || "") || I.error("unsupported lang: " + n), n = n.replace(H, q).toLowerCase(), function (e) { var t; do { if (t = T ? e.lang : e.getAttribute("xml:lang") || e.getAttribute("lang")) return (t = t.toLowerCase()) === n || 0 === t.indexOf(n + "-") } while ((e = e.parentNode) && 1 === e.nodeType); return !1 } }), target: function (e) { var t = ie.location && ie.location.hash; return t && t.slice(1) === e.id }, root: function (e) { return e === r }, focus: function (e) { return e === function () { try { return C.activeElement } catch (e) { } }() && C.hasFocus() && !!(e.type || e.href || ~e.tabIndex) }, enabled: z(!1), disabled: z(!0), checked: function (e) { return fe(e, "input") && !!e.checked || fe(e, "option") && !!e.selected }, selected: function (e) { return e.parentNode && e.parentNode.selectedIndex, !0 === e.selected }, empty: function (e) { for (e = e.firstChild; e; e = e.nextSibling)if (e.nodeType < 6) return !1; return !0 }, parent: function (e) { return !x.pseudos.empty(e) }, header: function (e) { return j.test(e.nodeName) }, input: function (e) { return L.test(e.nodeName) }, button: function (e) { return fe(e, "input") && "button" === e.type || fe(e, "button") }, text: function (e) { var t; return fe(e, "input") && "text" === e.type && (null == (t = e.getAttribute("type")) || "text" === t.toLowerCase()) }, first: U(function () { return [0] }), last: U(function (e, t) { return [t - 1] }), eq: U(function (e, t, n) { return [n < 0 ? n + t : n] }), even: U(function (e, t) { for (var n = 0; n < t; n += 2)e.push(n); return e }), odd: U(function (e, t) { for (var n = 1; n < t; n += 2)e.push(n); return e }), lt: U(function (e, t, n) { var r; for (r = n < 0 ? n + t : t < n ? t : n; 0 <= --r;)e.push(r); return e }), gt: U(function (e, t, n) { for (var r = n < 0 ? n + t : n; ++r < t;)e.push(r); return e }) } }).pseudos.nth = x.pseudos.eq, { radio: !0, checkbox: !0, file: !0, password: !0, image: !0 }) x.pseudos[e] = $(e); for (e in { submit: !0, reset: !0 }) x.pseudos[e] = _(e); function Q() { } function Y(e, t) { var n, r, i, o, a, s, u, l = c[e + " "]; if (l) return t ? 0 : l.slice(0); a = e, s = [], u = x.preFilter; while (a) { for (o in n && !(r = y.exec(a)) || (r && (a = a.slice(r[0].length) || a), s.push(i = [])), n = !1, (r = m.exec(a)) && (n = r.shift(), i.push({ value: n, type: r[0].replace(ve, " ") }), a = a.slice(n.length)), x.filter) !(r = N[o].exec(a)) || u[o] && !(r = u[o](r)) || (n = r.shift(), i.push({ value: n, type: o, matches: r }), a = a.slice(n.length)); if (!n) break } return t ? a.length : a ? I.error(e) : c(e, s).slice(0) } function G(e) { for (var t = 0, n = e.length, r = ""; t < n; t++)r += e[t].value; return r } function K(a, e, t) { var s = e.dir, u = e.next, l = u || s, c = t && "parentNode" === l, f = n++; return e.first ? function (e, t, n) { while (e = e[s]) if (1 === e.nodeType || c) return a(e, t, n); return !1 } : function (e, t, n) { var r, i, o = [S, f]; if (n) { while (e = e[s]) if ((1 === e.nodeType || c) && a(e, t, n)) return !0 } else while (e = e[s]) if (1 === e.nodeType || c) if (i = e[k] || (e[k] = {}), u && fe(e, u)) e = e[s] || e; else { if ((r = i[l]) && r[0] === S && r[1] === f) return o[2] = r[2]; if ((i[l] = o)[2] = a(e, t, n)) return !0 } return !1 } } function J(i) { return 1 < i.length ? function (e, t, n) { var r = i.length; while (r--) if (!i[r](e, t, n)) return !1; return !0 } : i[0] } function Z(e, t, n, r, i) { for (var o, a = [], s = 0, u = e.length, l = null != t; s < u; s++)(o = e[s]) && (n && !n(o, r, i) || (a.push(o), l && t.push(s))); return a } function ee(p, h, g, v, y, e) { return v && !v[k] && (v = ee(v)), y && !y[k] && (y = ee(y, e)), B(function (e, t, n, r) { var i, o, a, s, u = [], l = [], c = t.length, f = e || function (e, t, n) { for (var r = 0, i = t.length; r < i; r++)I(e, t[r], n); return n }(h || "*", n.nodeType ? [n] : n, []), d = !p || !e && h ? f : Z(f, u, p, n, r); if (g ? g(d, s = y || (e ? p : c || v) ? [] : t, n, r) : s = d, v) { i = Z(s, l), v(i, [], n, r), o = i.length; while (o--) (a = i[o]) && (s[l[o]] = !(d[l[o]] = a)) } if (e) { if (y || p) { if (y) { i = [], o = s.length; while (o--) (a = s[o]) && i.push(d[o] = a); y(null, s = [], i, r) } o = s.length; while (o--) (a = s[o]) && -1 < (i = y ? se.call(e, a) : u[o]) && (e[i] = !(t[i] = a)) } } else s = Z(s === t ? s.splice(c, s.length) : s), y ? y(null, t, s, r) : E.apply(t, s) }) } function te(e) { for (var i, t, n, r = e.length, o = x.relative[e[0].type], a = o || x.relative[" "], s = o ? 1 : 0, u = K(function (e) { return e === i }, a, !0), l = K(function (e) { return -1 < se.call(i, e) }, a, !0), c = [function (e, t, n) { var r = !o && (n || t != w) || ((i = t).nodeType ? u(e, t, n) : l(e, t, n)); return i = null, r }]; s < r; s++)if (t = x.relative[e[s].type]) c = [K(J(c), t)]; else { if ((t = x.filter[e[s].type].apply(null, e[s].matches))[k]) { for (n = ++s; n < r; n++)if (x.relative[e[n].type]) break; return ee(1 < s && J(c), 1 < s && G(e.slice(0, s - 1).concat({ value: " " === e[s - 2].type ? "*" : "" })).replace(ve, "$1"), t, s < n && te(e.slice(s, n)), n < r && te(e = e.slice(n)), n < r && G(e)) } c.push(t) } return J(c) } function ne(e, t) { var n, v, y, m, b, r, i = [], o = [], a = u[e + " "]; if (!a) { t || (t = Y(e)), n = t.length; while (n--) (a = te(t[n]))[k] ? i.push(a) : o.push(a); (a = u(e, (v = o, m = 0 < (y = i).length, b = 0 < v.length, r = function (e, t, n, r, i) { var o, a, s, u = 0, l = "0", c = e && [], f = [], d = w, p = e || b && x.find.TAG("*", i), h = S += null == d ? 1 : Math.random() || .1, g = p.length; for (i && (w = t == C || t || i); l !== g && null != (o = p[l]); l++) { if (b && o) { a = 0, t || o.ownerDocument == C || (V(o), n = !T); while (s = v[a++]) if (s(o, t || C, n)) { E.call(r, o); break } i && (S = h) } m && ((o = !s && o) && u--, e && c.push(o)) } if (u += l, m && l !== u) { a = 0; while (s = y[a++]) s(c, f, t, n); if (e) { if (0 < u) while (l--) c[l] || f[l] || (f[l] = de.call(r)); f = Z(f) } E.apply(r, f), i && !e && 0 < f.length && 1 < u + y.length && ce.uniqueSort(r) } return i && (S = h, w = d), c }, m ? B(r) : r))).selector = e } return a } function re(e, t, n, r) { var i, o, a, s, u, l = "function" == typeof e && e, c = !r && Y(e = l.selector || e); if (n = n || [], 1 === c.length) { if (2 < (o = c[0] = c[0].slice(0)).length && "ID" === (a = o[0]).type && 9 === t.nodeType && T && x.relative[o[1].type]) { if (!(t = (x.find.ID(a.matches[0].replace(H, q), t) || [])[0])) return n; l && (t = t.parentNode), e = e.slice(o.shift().value.length) } i = N.needsContext.test(e) ? 0 : o.length; while (i--) { if (a = o[i], x.relative[s = a.type]) break; if ((u = x.find[s]) && (r = u(a.matches[0].replace(H, q), P.test(o[0].type) && X(t.parentNode) || t))) { if (o.splice(i, 1), !(e = r.length && G(o))) return E.apply(n, r), n; break } } } return (l || ne(e, c))(r, t, !T, n, !t || P.test(e) && X(t.parentNode) || t), n } Q.prototype = x.filters = x.pseudos, x.setFilters = new Q, le.sortStable = k.split("").sort(l).join("") === k, V(), le.sortDetached = F(function (e) { return 1 & e.compareDocumentPosition(C.createElement("fieldset")) }), ce.find = I, ce.expr[":"] = ce.expr.pseudos, ce.unique = ce.uniqueSort, I.compile = ne, I.select = re, I.setDocument = V, I.tokenize = Y, I.escape = ce.escapeSelector, I.getText = ce.text, I.isXML = ce.isXMLDoc, I.selectors = ce.expr, I.support = ce.support, I.uniqueSort = ce.uniqueSort }(); var p = function (e, t, n) { var r = [], i = void 0 !== n; while ((e = e[t]) && 9 !== e.nodeType) if (1 === e.nodeType) { if (i && ce(e).is(n)) break; r.push(e) } return r }, h = function (e, t) { for (var n = []; e; e = e.nextSibling)1 === e.nodeType && e !== t && n.push(e); return n }, w = ce.expr.match.needsContext, C = /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i; function T(e, n, r) { return v(n) ? ce.grep(e, function (e, t) { return !!n.call(e, t, e) !== r }) : n.nodeType ? ce.grep(e, function (e) { return e === n !== r }) : "string" != typeof n ? ce.grep(e, function (e) { return -1 < se.call(n, e) !== r }) : ce.filter(n, e, r) } ce.filter = function (e, t, n) { var r = t[0]; return n && (e = ":not(" + e + ")"), 1 === t.length && 1 === r.nodeType ? ce.find.matchesSelector(r, e) ? [r] : [] : ce.find.matches(e, ce.grep(t, function (e) { return 1 === e.nodeType })) }, ce.fn.extend({ find: function (e) { var t, n, r = this.length, i = this; if ("string" != typeof e) return this.pushStack(ce(e).filter(function () { for (t = 0; t < r; t++)if (ce.contains(i[t], this)) return !0 })); for (n = this.pushStack([]), t = 0; t < r; t++)ce.find(e, i[t], n); return 1 < r ? ce.uniqueSort(n) : n }, filter: function (e) { return this.pushStack(T(this, e || [], !1)) }, not: function (e) { return this.pushStack(T(this, e || [], !0)) }, is: function (e) { return !!T(this, "string" == typeof e && w.test(e) ? ce(e) : e || [], !1).length } }); var E, k = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/; (ce.fn.init = function (e, t, n) { var r, i; if (!e) return this; if (n = n || E, "string" == typeof e) { if (!(r = "<" === e[0] && ">" === e[e.length - 1] && 3 <= e.length ? [null, e, null] : k.exec(e)) || !r[1] && t) return !t || t.jquery ? (t || n).find(e) : this.constructor(t).find(e); if (r[1]) { if (t = t instanceof ce ? t[0] : t, ce.merge(this, ce.parseHTML(r[1], t && t.nodeType ? t.ownerDocument || t : m, !0)), C.test(r[1]) && ce.isPlainObject(t)) for (r in t) v(this[r]) ? this[r](t[r]) : this.attr(r, t[r]); return this } return (i = m.getElementById(r[2])) && (this[0] = i, this.length = 1), this } return e.nodeType ? (this[0] = e, this.length = 1, this) : v(e) ? void 0 !== n.ready ? n.ready(e) : e(ce) : ce.makeArray(e, this) }).prototype = ce.fn, E = ce(m); var S = /^(?:parents|prev(?:Until|All))/, A = { children: !0, contents: !0, next: !0, prev: !0 }; function D(e, t) { while ((e = e[t]) && 1 !== e.nodeType); return e } ce.fn.extend({ has: function (e) { var t = ce(e, this), n = t.length; return this.filter(function () { for (var e = 0; e < n; e++)if (ce.contains(this, t[e])) return !0 }) }, closest: function (e, t) { var n, r = 0, i = this.length, o = [], a = "string" != typeof e && ce(e); if (!w.test(e)) for (; r < i; r++)for (n = this[r]; n && n !== t; n = n.parentNode)if (n.nodeType < 11 && (a ? -1 < a.index(n) : 1 === n.nodeType && ce.find.matchesSelector(n, e))) { o.push(n); break } return this.pushStack(1 < o.length ? ce.uniqueSort(o) : o) }, index: function (e) { return e ? "string" == typeof e ? se.call(ce(e), this[0]) : se.call(this, e.jquery ? e[0] : e) : this[0] && this[0].parentNode ? this.first().prevAll().length : -1 }, add: function (e, t) { return this.pushStack(ce.uniqueSort(ce.merge(this.get(), ce(e, t)))) }, addBack: function (e) { return this.add(null == e ? this.prevObject : this.prevObject.filter(e)) } }), ce.each({ parent: function (e) { var t = e.parentNode; return t && 11 !== t.nodeType ? t : null }, parents: function (e) { return p(e, "parentNode") }, parentsUntil: function (e, t, n) { return p(e, "parentNode", n) }, next: function (e) { return D(e, "nextSibling") }, prev: function (e) { return D(e, "previousSibling") }, nextAll: function (e) { return p(e, "nextSibling") }, prevAll: function (e) { return p(e, "previousSibling") }, nextUntil: function (e, t, n) { return p(e, "nextSibling", n) }, prevUntil: function (e, t, n) { return p(e, "previousSibling", n) }, siblings: function (e) { return h((e.parentNode || {}).firstChild, e) }, children: function (e) { return h(e.firstChild) }, contents: function (e) { return null != e.contentDocument && r(e.contentDocument) ? e.contentDocument : (fe(e, "template") && (e = e.content || e), ce.merge([], e.childNodes)) } }, function (r, i) { ce.fn[r] = function (e, t) { var n = ce.map(this, i, e); return "Until" !== r.slice(-5) && (t = e), t && "string" == typeof t && (n = ce.filter(t, n)), 1 < this.length && (A[r] || ce.uniqueSort(n), S.test(r) && n.reverse()), this.pushStack(n) } }); var N = /[^\x20\t\r\n\f]+/g; function L(e) { return e } function j(e) { throw e } function O(e, t, n, r) { var i; try { e && v(i = e.promise) ? i.call(e).done(t).fail(n) : e && v(i = e.then) ? i.call(e, t, n) : t.apply(void 0, [e].slice(r)) } catch (e) { n.apply(void 0, [e]) } } ce.Callbacks = function (r) { var e, n; r = "string" == typeof r ? (e = r, n = {}, ce.each(e.match(N) || [], function (e, t) { n[t] = !0 }), n) : ce.extend({}, r); var i, t, o, a, s = [], u = [], l = -1, c = function () { for (a = a || r.once, o = i = !0; u.length; l = -1) { t = u.shift(); while (++l < s.length) !1 === s[l].apply(t[0], t[1]) && r.stopOnFalse && (l = s.length, t = !1) } r.memory || (t = !1), i = !1, a && (s = t ? [] : "") }, f = { add: function () { return s && (t && !i && (l = s.length - 1, u.push(t)), function n(e) { ce.each(e, function (e, t) { v(t) ? r.unique && f.has(t) || s.push(t) : t && t.length && "string" !== x(t) && n(t) }) }(arguments), t && !i && c()), this }, remove: function () { return ce.each(arguments, function (e, t) { var n; while (-1 < (n = ce.inArray(t, s, n))) s.splice(n, 1), n <= l && l-- }), this }, has: function (e) { return e ? -1 < ce.inArray(e, s) : 0 < s.length }, empty: function () { return s && (s = []), this }, disable: function () { return a = u = [], s = t = "", this }, disabled: function () { return !s }, lock: function () { return a = u = [], t || i || (s = t = ""), this }, locked: function () { return !!a }, fireWith: function (e, t) { return a || (t = [e, (t = t || []).slice ? t.slice() : t], u.push(t), i || c()), this }, fire: function () { return f.fireWith(this, arguments), this }, fired: function () { return !!o } }; return f }, ce.extend({ Deferred: function (e) { var o = [["notify", "progress", ce.Callbacks("memory"), ce.Callbacks("memory"), 2], ["resolve", "done", ce.Callbacks("once memory"), ce.Callbacks("once memory"), 0, "resolved"], ["reject", "fail", ce.Callbacks("once memory"), ce.Callbacks("once memory"), 1, "rejected"]], i = "pending", a = { state: function () { return i }, always: function () { return s.done(arguments).fail(arguments), this }, "catch": function (e) { return a.then(null, e) }, pipe: function () { var i = arguments; return ce.Deferred(function (r) { ce.each(o, function (e, t) { var n = v(i[t[4]]) && i[t[4]]; s[t[1]](function () { var e = n && n.apply(this, arguments); e && v(e.promise) ? e.promise().progress(r.notify).done(r.resolve).fail(r.reject) : r[t[0] + "With"](this, n ? [e] : arguments) }) }), i = null }).promise() }, then: function (t, n, r) { var u = 0; function l(i, o, a, s) { return function () { var n = this, r = arguments, e = function () { var e, t; if (!(i < u)) { if ((e = a.apply(n, r)) === o.promise()) throw new TypeError("Thenable self-resolution"); t = e && ("object" == typeof e || "function" == typeof e) && e.then, v(t) ? s ? t.call(e, l(u, o, L, s), l(u, o, j, s)) : (u++, t.call(e, l(u, o, L, s), l(u, o, j, s), l(u, o, L, o.notifyWith))) : (a !== L && (n = void 0, r = [e]), (s || o.resolveWith)(n, r)) } }, t = s ? e : function () { try { e() } catch (e) { ce.Deferred.exceptionHook && ce.Deferred.exceptionHook(e, t.error), u <= i + 1 && (a !== j && (n = void 0, r = [e]), o.rejectWith(n, r)) } }; i ? t() : (ce.Deferred.getErrorHook ? t.error = ce.Deferred.getErrorHook() : ce.Deferred.getStackHook && (t.error = ce.Deferred.getStackHook()), ie.setTimeout(t)) } } return ce.Deferred(function (e) { o[0][3].add(l(0, e, v(r) ? r : L, e.notifyWith)), o[1][3].add(l(0, e, v(t) ? t : L)), o[2][3].add(l(0, e, v(n) ? n : j)) }).promise() }, promise: function (e) { return null != e ? ce.extend(e, a) : a } }, s = {}; return ce.each(o, function (e, t) { var n = t[2], r = t[5]; a[t[1]] = n.add, r && n.add(function () { i = r }, o[3 - e][2].disable, o[3 - e][3].disable, o[0][2].lock, o[0][3].lock), n.add(t[3].fire), s[t[0]] = function () { return s[t[0] + "With"](this === s ? void 0 : this, arguments), this }, s[t[0] + "With"] = n.fireWith }), a.promise(s), e && e.call(s, s), s }, when: function (e) { var n = arguments.length, t = n, r = Array(t), i = ae.call(arguments), o = ce.Deferred(), a = function (t) { return function (e) { r[t] = this, i[t] = 1 < arguments.length ? ae.call(arguments) : e, --n || o.resolveWith(r, i) } }; if (n <= 1 && (O(e, o.done(a(t)).resolve, o.reject, !n), "pending" === o.state() || v(i[t] && i[t].then))) return o.then(); while (t--) O(i[t], a(t), o.reject); return o.promise() } }); var P = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; ce.Deferred.exceptionHook = function (e, t) { ie.console && ie.console.warn && e && P.test(e.name) && ie.console.warn("jQuery.Deferred exception: " + e.message, e.stack, t) }, ce.readyException = function (e) { ie.setTimeout(function () { throw e }) }; var H = ce.Deferred(); function q() { m.removeEventListener("DOMContentLoaded", q), ie.removeEventListener("load", q), ce.ready() } ce.fn.ready = function (e) { return H.then(e)["catch"](function (e) { ce.readyException(e) }), this }, ce.extend({ isReady: !1, readyWait: 1, ready: function (e) { (!0 === e ? --ce.readyWait : ce.isReady) || (ce.isReady = !0) !== e && 0 < --ce.readyWait || H.resolveWith(m, [ce]) } }), ce.ready.then = H.then, "complete" === m.readyState || "loading" !== m.readyState && !m.documentElement.doScroll ? ie.setTimeout(ce.ready) : (m.addEventListener("DOMContentLoaded", q), ie.addEventListener("load", q)); var R = function (e, t, n, r, i, o, a) { var s = 0, u = e.length, l = null == n; if ("object" === x(n)) for (s in i = !0, n) R(e, t, s, n[s], !0, o, a); else if (void 0 !== r && (i = !0, v(r) || (a = !0), l && (a ? (t.call(e, r), t = null) : (l = t, t = function (e, t, n) { return l.call(ce(e), n) })), t)) for (; s < u; s++)t(e[s], n, a ? r : r.call(e[s], s, t(e[s], n))); return i ? e : l ? t.call(e) : u ? t(e[0], n) : o }, M = /^-ms-/, I = /-([a-z])/g; function W(e, t) { return t.toUpperCase() } function B(e) { return e.replace(M, "ms-").replace(I, W) } var F = function (e) { return 1 === e.nodeType || 9 === e.nodeType || !+e.nodeType }; function $() { this.expando = ce.expando + $.uid++ } $.uid = 1, $.prototype = { cache: function (e) { var t = e[this.expando]; return t || (t = {}, F(e) && (e.nodeType ? e[this.expando] = t : Object.defineProperty(e, this.expando, { value: t, configurable: !0 }))), t }, set: function (e, t, n) { var r, i = this.cache(e); if ("string" == typeof t) i[B(t)] = n; else for (r in t) i[B(r)] = t[r]; return i }, get: function (e, t) { return void 0 === t ? this.cache(e) : e[this.expando] && e[this.expando][B(t)] }, access: function (e, t, n) { return void 0 === t || t && "string" == typeof t && void 0 === n ? this.get(e, t) : (this.set(e, t, n), void 0 !== n ? n : t) }, remove: function (e, t) { var n, r = e[this.expando]; if (void 0 !== r) { if (void 0 !== t) { n = (t = Array.isArray(t) ? t.map(B) : (t = B(t)) in r ? [t] : t.match(N) || []).length; while (n--) delete r[t[n]] } (void 0 === t || ce.isEmptyObject(r)) && (e.nodeType ? e[this.expando] = void 0 : delete e[this.expando]) } }, hasData: function (e) { var t = e[this.expando]; return void 0 !== t && !ce.isEmptyObject(t) } }; var _ = new $, z = new $, U = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, X = /[A-Z]/g; function V(e, t, n) { var r, i; if (void 0 === n && 1 === e.nodeType) if (r = "data-" + t.replace(X, "-$&").toLowerCase(), "string" == typeof (n = e.getAttribute(r))) { try { n = "true" === (i = n) || "false" !== i && ("null" === i ? null : i === +i + "" ? +i : U.test(i) ? JSON.parse(i) : i) } catch (e) { } z.set(e, t, n) } else n = void 0; return n } ce.extend({ hasData: function (e) { return z.hasData(e) || _.hasData(e) }, data: function (e, t, n) { return z.access(e, t, n) }, removeData: function (e, t) { z.remove(e, t) }, _data: function (e, t, n) { return _.access(e, t, n) }, _removeData: function (e, t) { _.remove(e, t) } }), ce.fn.extend({ data: function (n, e) { var t, r, i, o = this[0], a = o && o.attributes; if (void 0 === n) { if (this.length && (i = z.get(o), 1 === o.nodeType && !_.get(o, "hasDataAttrs"))) { t = a.length; while (t--) a[t] && 0 === (r = a[t].name).indexOf("data-") && (r = B(r.slice(5)), V(o, r, i[r])); _.set(o, "hasDataAttrs", !0) } return i } return "object" == typeof n ? this.each(function () { z.set(this, n) }) : R(this, function (e) { var t; if (o && void 0 === e) return void 0 !== (t = z.get(o, n)) ? t : void 0 !== (t = V(o, n)) ? t : void 0; this.each(function () { z.set(this, n, e) }) }, null, e, 1 < arguments.length, null, !0) }, removeData: function (e) { return this.each(function () { z.remove(this, e) }) } }), ce.extend({ queue: function (e, t, n) { var r; if (e) return t = (t || "fx") + "queue", r = _.get(e, t), n && (!r || Array.isArray(n) ? r = _.access(e, t, ce.makeArray(n)) : r.push(n)), r || [] }, dequeue: function (e, t) { t = t || "fx"; var n = ce.queue(e, t), r = n.length, i = n.shift(), o = ce._queueHooks(e, t); "inprogress" === i && (i = n.shift(), r--), i && ("fx" === t && n.unshift("inprogress"), delete o.stop, i.call(e, function () { ce.dequeue(e, t) }, o)), !r && o && o.empty.fire() }, _queueHooks: function (e, t) { var n = t + "queueHooks"; return _.get(e, n) || _.access(e, n, { empty: ce.Callbacks("once memory").add(function () { _.remove(e, [t + "queue", n]) }) }) } }), ce.fn.extend({ queue: function (t, n) { var e = 2; return "string" != typeof t && (n = t, t = "fx", e--), arguments.length < e ? ce.queue(this[0], t) : void 0 === n ? this : this.each(function () { var e = ce.queue(this, t, n); ce._queueHooks(this, t), "fx" === t && "inprogress" !== e[0] && ce.dequeue(this, t) }) }, dequeue: function (e) { return this.each(function () { ce.dequeue(this, e) }) }, clearQueue: function (e) { return this.queue(e || "fx", []) }, promise: function (e, t) { var n, r = 1, i = ce.Deferred(), o = this, a = this.length, s = function () { --r || i.resolveWith(o, [o]) }; "string" != typeof e && (t = e, e = void 0), e = e || "fx"; while (a--) (n = _.get(o[a], e + "queueHooks")) && n.empty && (r++, n.empty.add(s)); return s(), i.promise(t) } }); var Q = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, Y = new RegExp("^(?:([+-])=|)(" + Q + ")([a-z%]*)$", "i"), G = ["Top", "Right", "Bottom", "Left"], K = m.documentElement, J = function (e) { return ce.contains(e.ownerDocument, e) }, Z = { composed: !0 }; K.getRootNode && (J = function (e) { return ce.contains(e.ownerDocument, e) || e.getRootNode(Z) === e.ownerDocument }); var ee = function (e, t) { return "none" === (e = t || e).style.display || "" === e.style.display && J(e) && "none" === ce.css(e, "display") }; var te = {}; function ne(e, t) { for (var n, r, i, o, a, s, u, l = [], c = 0, f = e.length; c < f; c++)(r = e[c]).style && (n = r.style.display, t ? ("none" === n && (l[c] = _.get(r, "display") || null, l[c] || (r.style.display = "")), "" === r.style.display && ee(r) && (l[c] = (u = a = o = void 0, a = (i = r).ownerDocument, s = i.nodeName, (u = te[s]) || (o = a.body.appendChild(a.createElement(s)), u = ce.css(o, "display"), o.parentNode.removeChild(o), "none" === u && (u = "block"), te[s] = u)))) : "none" !== n && (l[c] = "none", _.set(r, "display", n))); for (c = 0; c < f; c++)null != l[c] && (e[c].style.display = l[c]); return e } ce.fn.extend({ show: function () { return ne(this, !0) }, hide: function () { return ne(this) }, toggle: function (e) { return "boolean" == typeof e ? e ? this.show() : this.hide() : this.each(function () { ee(this) ? ce(this).show() : ce(this).hide() }) } }); var re, be, xe = /^(?:checkbox|radio)$/i, we = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i, Ce = /^$|^module$|\/(?:java|ecma)script/i; re = m.createDocumentFragment().appendChild(m.createElement("div")), (be = m.createElement("input")).setAttribute("type", "radio"), be.setAttribute("checked", "checked"), be.setAttribute("name", "t"), re.appendChild(be), le.checkClone = re.cloneNode(!0).cloneNode(!0).lastChild.checked, re.innerHTML = "<textarea>x</textarea>", le.noCloneChecked = !!re.cloneNode(!0).lastChild.defaultValue, re.innerHTML = "<option></option>", le.option = !!re.lastChild; var Te = { thead: [1, "<table>", "</table>"], col: [2, "<table><colgroup>", "</colgroup></table>"], tr: [2, "<table><tbody>", "</tbody></table>"], td: [3, "<table><tbody><tr>", "</tr></tbody></table>"], _default: [0, "", ""] }; function Ee(e, t) { var n; return n = "undefined" != typeof e.getElementsByTagName ? e.getElementsByTagName(t || "*") : "undefined" != typeof e.querySelectorAll ? e.querySelectorAll(t || "*") : [], void 0 === t || t && fe(e, t) ? ce.merge([e], n) : n } function ke(e, t) { for (var n = 0, r = e.length; n < r; n++)_.set(e[n], "globalEval", !t || _.get(t[n], "globalEval")) } Te.tbody = Te.tfoot = Te.colgroup = Te.caption = Te.thead, Te.th = Te.td, le.option || (Te.optgroup = Te.option = [1, "<select multiple='multiple'>", "</select>"]); var Se = /<|&#?\w+;/; function Ae(e, t, n, r, i) { for (var o, a, s, u, l, c, f = t.createDocumentFragment(), d = [], p = 0, h = e.length; p < h; p++)if ((o = e[p]) || 0 === o) if ("object" === x(o)) ce.merge(d, o.nodeType ? [o] : o); else if (Se.test(o)) { a = a || f.appendChild(t.createElement("div")), s = (we.exec(o) || ["", ""])[1].toLowerCase(), u = Te[s] || Te._default, a.innerHTML = u[1] + ce.htmlPrefilter(o) + u[2], c = u[0]; while (c--) a = a.lastChild; ce.merge(d, a.childNodes), (a = f.firstChild).textContent = "" } else d.push(t.createTextNode(o)); f.textContent = "", p = 0; while (o = d[p++]) if (r && -1 < ce.inArray(o, r)) i && i.push(o); else if (l = J(o), a = Ee(f.appendChild(o), "script"), l && ke(a), n) { c = 0; while (o = a[c++]) Ce.test(o.type || "") && n.push(o) } return f } var De = /^([^.]*)(?:\.(.+)|)/; function Ne() { return !0 } function Le() { return !1 } function je(e, t, n, r, i, o) { var a, s; if ("object" == typeof t) { for (s in "string" != typeof n && (r = r || n, n = void 0), t) je(e, s, n, r, t[s], o); return e } if (null == r && null == i ? (i = n, r = n = void 0) : null == i && ("string" == typeof n ? (i = r, r = void 0) : (i = r, r = n, n = void 0)), !1 === i) i = Le; else if (!i) return e; return 1 === o && (a = i, (i = function (e) { return ce().off(e), a.apply(this, arguments) }).guid = a.guid || (a.guid = ce.guid++)), e.each(function () { ce.event.add(this, t, i, r, n) }) } function Oe(e, r, t) { t ? (_.set(e, r, !1), ce.event.add(e, r, { namespace: !1, handler: function (e) { var t, n = _.get(this, r); if (1 & e.isTrigger && this[r]) { if (n) (ce.event.special[r] || {}).delegateType && e.stopPropagation(); else if (n = ae.call(arguments), _.set(this, r, n), this[r](), t = _.get(this, r), _.set(this, r, !1), n !== t) return e.stopImmediatePropagation(), e.preventDefault(), t } else n && (_.set(this, r, ce.event.trigger(n[0], n.slice(1), this)), e.stopPropagation(), e.isImmediatePropagationStopped = Ne) } })) : void 0 === _.get(e, r) && ce.event.add(e, r, Ne) } ce.event = { global: {}, add: function (t, e, n, r, i) { var o, a, s, u, l, c, f, d, p, h, g, v = _.get(t); if (F(t)) { n.handler && (n = (o = n).handler, i = o.selector), i && ce.find.matchesSelector(K, i), n.guid || (n.guid = ce.guid++), (u = v.events) || (u = v.events = Object.create(null)), (a = v.handle) || (a = v.handle = function (e) { return "undefined" != typeof ce && ce.event.triggered !== e.type ? ce.event.dispatch.apply(t, arguments) : void 0 }), l = (e = (e || "").match(N) || [""]).length; while (l--) p = g = (s = De.exec(e[l]) || [])[1], h = (s[2] || "").split(".").sort(), p && (f = ce.event.special[p] || {}, p = (i ? f.delegateType : f.bindType) || p, f = ce.event.special[p] || {}, c = ce.extend({ type: p, origType: g, data: r, handler: n, guid: n.guid, selector: i, needsContext: i && ce.expr.match.needsContext.test(i), namespace: h.join(".") }, o), (d = u[p]) || ((d = u[p] = []).delegateCount = 0, f.setup && !1 !== f.setup.call(t, r, h, a) || t.addEventListener && t.addEventListener(p, a)), f.add && (f.add.call(t, c), c.handler.guid || (c.handler.guid = n.guid)), i ? d.splice(d.delegateCount++, 0, c) : d.push(c), ce.event.global[p] = !0) } }, remove: function (e, t, n, r, i) { var o, a, s, u, l, c, f, d, p, h, g, v = _.hasData(e) && _.get(e); if (v && (u = v.events)) { l = (t = (t || "").match(N) || [""]).length; while (l--) if (p = g = (s = De.exec(t[l]) || [])[1], h = (s[2] || "").split(".").sort(), p) { f = ce.event.special[p] || {}, d = u[p = (r ? f.delegateType : f.bindType) || p] || [], s = s[2] && new RegExp("(^|\\.)" + h.join("\\.(?:.*\\.|)") + "(\\.|$)"), a = o = d.length; while (o--) c = d[o], !i && g !== c.origType || n && n.guid !== c.guid || s && !s.test(c.namespace) || r && r !== c.selector && ("**" !== r || !c.selector) || (d.splice(o, 1), c.selector && d.delegateCount--, f.remove && f.remove.call(e, c)); a && !d.length && (f.teardown && !1 !== f.teardown.call(e, h, v.handle) || ce.removeEvent(e, p, v.handle), delete u[p]) } else for (p in u) ce.event.remove(e, p + t[l], n, r, !0); ce.isEmptyObject(u) && _.remove(e, "handle events") } }, dispatch: function (e) { var t, n, r, i, o, a, s = new Array(arguments.length), u = ce.event.fix(e), l = (_.get(this, "events") || Object.create(null))[u.type] || [], c = ce.event.special[u.type] || {}; for (s[0] = u, t = 1; t < arguments.length; t++)s[t] = arguments[t]; if (u.delegateTarget = this, !c.preDispatch || !1 !== c.preDispatch.call(this, u)) { a = ce.event.handlers.call(this, u, l), t = 0; while ((i = a[t++]) && !u.isPropagationStopped()) { u.currentTarget = i.elem, n = 0; while ((o = i.handlers[n++]) && !u.isImmediatePropagationStopped()) u.rnamespace && !1 !== o.namespace && !u.rnamespace.test(o.namespace) || (u.handleObj = o, u.data = o.data, void 0 !== (r = ((ce.event.special[o.origType] || {}).handle || o.handler).apply(i.elem, s)) && !1 === (u.result = r) && (u.preventDefault(), u.stopPropagation())) } return c.postDispatch && c.postDispatch.call(this, u), u.result } }, handlers: function (e, t) { var n, r, i, o, a, s = [], u = t.delegateCount, l = e.target; if (u && l.nodeType && !("click" === e.type && 1 <= e.button)) for (; l !== this; l = l.parentNode || this)if (1 === l.nodeType && ("click" !== e.type || !0 !== l.disabled)) { for (o = [], a = {}, n = 0; n < u; n++)void 0 === a[i = (r = t[n]).selector + " "] && (a[i] = r.needsContext ? -1 < ce(i, this).index(l) : ce.find(i, this, null, [l]).length), a[i] && o.push(r); o.length && s.push({ elem: l, handlers: o }) } return l = this, u < t.length && s.push({ elem: l, handlers: t.slice(u) }), s }, addProp: function (t, e) { Object.defineProperty(ce.Event.prototype, t, { enumerable: !0, configurable: !0, get: v(e) ? function () { if (this.originalEvent) return e(this.originalEvent) } : function () { if (this.originalEvent) return this.originalEvent[t] }, set: function (e) { Object.defineProperty(this, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) } }) }, fix: function (e) { return e[ce.expando] ? e : new ce.Event(e) }, special: { load: { noBubble: !0 }, click: { setup: function (e) { var t = this || e; return xe.test(t.type) && t.click && fe(t, "input") && Oe(t, "click", !0), !1 }, trigger: function (e) { var t = this || e; return xe.test(t.type) && t.click && fe(t, "input") && Oe(t, "click"), !0 }, _default: function (e) { var t = e.target; return xe.test(t.type) && t.click && fe(t, "input") && _.get(t, "click") || fe(t, "a") } }, beforeunload: { postDispatch: function (e) { void 0 !== e.result && e.originalEvent && (e.originalEvent.returnValue = e.result) } } } }, ce.removeEvent = function (e, t, n) { e.removeEventListener && e.removeEventListener(t, n) }, ce.Event = function (e, t) { if (!(this instanceof ce.Event)) return new ce.Event(e, t); e && e.type ? (this.originalEvent = e, this.type = e.type, this.isDefaultPrevented = e.defaultPrevented || void 0 === e.defaultPrevented && !1 === e.returnValue ? Ne : Le, this.target = e.target && 3 === e.target.nodeType ? e.target.parentNode : e.target, this.currentTarget = e.currentTarget, this.relatedTarget = e.relatedTarget) : this.type = e, t && ce.extend(this, t), this.timeStamp = e && e.timeStamp || Date.now(), this[ce.expando] = !0 }, ce.Event.prototype = { constructor: ce.Event, isDefaultPrevented: Le, isPropagationStopped: Le, isImmediatePropagationStopped: Le, isSimulated: !1, preventDefault: function () { var e = this.originalEvent; this.isDefaultPrevented = Ne, e && !this.isSimulated && e.preventDefault() }, stopPropagation: function () { var e = this.originalEvent; this.isPropagationStopped = Ne, e && !this.isSimulated && e.stopPropagation() }, stopImmediatePropagation: function () { var e = this.originalEvent; this.isImmediatePropagationStopped = Ne, e && !this.isSimulated && e.stopImmediatePropagation(), this.stopPropagation() } }, ce.each({ altKey: !0, bubbles: !0, cancelable: !0, changedTouches: !0, ctrlKey: !0, detail: !0, eventPhase: !0, metaKey: !0, pageX: !0, pageY: !0, shiftKey: !0, view: !0, "char": !0, code: !0, charCode: !0, key: !0, keyCode: !0, button: !0, buttons: !0, clientX: !0, clientY: !0, offsetX: !0, offsetY: !0, pointerId: !0, pointerType: !0, screenX: !0, screenY: !0, targetTouches: !0, toElement: !0, touches: !0, which: !0 }, ce.event.addProp), ce.each({ focus: "focusin", blur: "focusout" }, function (r, i) { function o(e) { if (m.documentMode) { var t = _.get(this, "handle"), n = ce.event.fix(e); n.type = "focusin" === e.type ? "focus" : "blur", n.isSimulated = !0, t(e), n.target === n.currentTarget && t(n) } else ce.event.simulate(i, e.target, ce.event.fix(e)) } ce.event.special[r] = { setup: function () { var e; if (Oe(this, r, !0), !m.documentMode) return !1; (e = _.get(this, i)) || this.addEventListener(i, o), _.set(this, i, (e || 0) + 1) }, trigger: function () { return Oe(this, r), !0 }, teardown: function () { var e; if (!m.documentMode) return !1; (e = _.get(this, i) - 1) ? _.set(this, i, e) : (this.removeEventListener(i, o), _.remove(this, i)) }, _default: function (e) { return _.get(e.target, r) }, delegateType: i }, ce.event.special[i] = { setup: function () { var e = this.ownerDocument || this.document || this, t = m.documentMode ? this : e, n = _.get(t, i); n || (m.documentMode ? this.addEventListener(i, o) : e.addEventListener(r, o, !0)), _.set(t, i, (n || 0) + 1) }, teardown: function () { var e = this.ownerDocument || this.document || this, t = m.documentMode ? this : e, n = _.get(t, i) - 1; n ? _.set(t, i, n) : (m.documentMode ? this.removeEventListener(i, o) : e.removeEventListener(r, o, !0), _.remove(t, i)) } } }), ce.each({ mouseenter: "mouseover", mouseleave: "mouseout", pointerenter: "pointerover", pointerleave: "pointerout" }, function (e, i) { ce.event.special[e] = { delegateType: i, bindType: i, handle: function (e) { var t, n = e.relatedTarget, r = e.handleObj; return n && (n === this || ce.contains(this, n)) || (e.type = r.origType, t = r.handler.apply(this, arguments), e.type = i), t } } }), ce.fn.extend({ on: function (e, t, n, r) { return je(this, e, t, n, r) }, one: function (e, t, n, r) { return je(this, e, t, n, r, 1) }, off: function (e, t, n) { var r, i; if (e && e.preventDefault && e.handleObj) return r = e.handleObj, ce(e.delegateTarget).off(r.namespace ? r.origType + "." + r.namespace : r.origType, r.selector, r.handler), this; if ("object" == typeof e) { for (i in e) this.off(i, t, e[i]); return this } return !1 !== t && "function" != typeof t || (n = t, t = void 0), !1 === n && (n = Le), this.each(function () { ce.event.remove(this, e, n, t) }) } }); var Pe = /<script|<style|<link/i, He = /checked\s*(?:[^=]|=\s*.checked.)/i, qe = /^\s*<!\[CDATA\[|\]\]>\s*$/g; function Re(e, t) { return fe(e, "table") && fe(11 !== t.nodeType ? t : t.firstChild, "tr") && ce(e).children("tbody")[0] || e } function Me(e) { return e.type = (null !== e.getAttribute("type")) + "/" + e.type, e } function Ie(e) { return "true/" === (e.type || "").slice(0, 5) ? e.type = e.type.slice(5) : e.removeAttribute("type"), e } function We(e, t) { var n, r, i, o, a, s; if (1 === t.nodeType) { if (_.hasData(e) && (s = _.get(e).events)) for (i in _.remove(t, "handle events"), s) for (n = 0, r = s[i].length; n < r; n++)ce.event.add(t, i, s[i][n]); z.hasData(e) && (o = z.access(e), a = ce.extend({}, o), z.set(t, a)) } } function Be(n, r, i, o) { r = g(r); var e, t, a, s, u, l, c = 0, f = n.length, d = f - 1, p = r[0], h = v(p); if (h || 1 < f && "string" == typeof p && !le.checkClone && He.test(p)) return n.each(function (e) { var t = n.eq(e); h && (r[0] = p.call(this, e, t.html())), Be(t, r, i, o) }); if (f && (t = (e = Ae(r, n[0].ownerDocument, !1, n, o)).firstChild, 1 === e.childNodes.length && (e = t), t || o)) { for (s = (a = ce.map(Ee(e, "script"), Me)).length; c < f; c++)u = e, c !== d && (u = ce.clone(u, !0, !0), s && ce.merge(a, Ee(u, "script"))), i.call(n[c], u, c); if (s) for (l = a[a.length - 1].ownerDocument, ce.map(a, Ie), c = 0; c < s; c++)u = a[c], Ce.test(u.type || "") && !_.access(u, "globalEval") && ce.contains(l, u) && (u.src && "module" !== (u.type || "").toLowerCase() ? ce._evalUrl && !u.noModule && ce._evalUrl(u.src, { nonce: u.nonce || u.getAttribute("nonce") }, l) : b(u.textContent.replace(qe, ""), u, l)) } return n } function Fe(e, t, n) { for (var r, i = t ? ce.filter(t, e) : e, o = 0; null != (r = i[o]); o++)n || 1 !== r.nodeType || ce.cleanData(Ee(r)), r.parentNode && (n && J(r) && ke(Ee(r, "script")), r.parentNode.removeChild(r)); return e } ce.extend({ htmlPrefilter: function (e) { return e }, clone: function (e, t, n) { var r, i, o, a, s, u, l, c = e.cloneNode(!0), f = J(e); if (!(le.noCloneChecked || 1 !== e.nodeType && 11 !== e.nodeType || ce.isXMLDoc(e))) for (a = Ee(c), r = 0, i = (o = Ee(e)).length; r < i; r++)s = o[r], u = a[r], void 0, "input" === (l = u.nodeName.toLowerCase()) && xe.test(s.type) ? u.checked = s.checked : "input" !== l && "textarea" !== l || (u.defaultValue = s.defaultValue); if (t) if (n) for (o = o || Ee(e), a = a || Ee(c), r = 0, i = o.length; r < i; r++)We(o[r], a[r]); else We(e, c); return 0 < (a = Ee(c, "script")).length && ke(a, !f && Ee(e, "script")), c }, cleanData: function (e) { for (var t, n, r, i = ce.event.special, o = 0; void 0 !== (n = e[o]); o++)if (F(n)) { if (t = n[_.expando]) { if (t.events) for (r in t.events) i[r] ? ce.event.remove(n, r) : ce.removeEvent(n, r, t.handle); n[_.expando] = void 0 } n[z.expando] && (n[z.expando] = void 0) } } }), ce.fn.extend({ detach: function (e) { return Fe(this, e, !0) }, remove: function (e) { return Fe(this, e) }, text: function (e) { return R(this, function (e) { return void 0 === e ? ce.text(this) : this.empty().each(function () { 1 !== this.nodeType && 11 !== this.nodeType && 9 !== this.nodeType || (this.textContent = e) }) }, null, e, arguments.length) }, append: function () { return Be(this, arguments, function (e) { 1 !== this.nodeType && 11 !== this.nodeType && 9 !== this.nodeType || Re(this, e).appendChild(e) }) }, prepend: function () { return Be(this, arguments, function (e) { if (1 === this.nodeType || 11 === this.nodeType || 9 === this.nodeType) { var t = Re(this, e); t.insertBefore(e, t.firstChild) } }) }, before: function () { return Be(this, arguments, function (e) { this.parentNode && this.parentNode.insertBefore(e, this) }) }, after: function () { return Be(this, arguments, function (e) { this.parentNode && this.parentNode.insertBefore(e, this.nextSibling) }) }, empty: function () { for (var e, t = 0; null != (e = this[t]); t++)1 === e.nodeType && (ce.cleanData(Ee(e, !1)), e.textContent = ""); return this }, clone: function (e, t) { return e = null != e && e, t = null == t ? e : t, this.map(function () { return ce.clone(this, e, t) }) }, html: function (e) { return R(this, function (e) { var t = this[0] || {}, n = 0, r = this.length; if (void 0 === e && 1 === t.nodeType) return t.innerHTML; if ("string" == typeof e && !Pe.test(e) && !Te[(we.exec(e) || ["", ""])[1].toLowerCase()]) { e = ce.htmlPrefilter(e); try { for (; n < r; n++)1 === (t = this[n] || {}).nodeType && (ce.cleanData(Ee(t, !1)), t.innerHTML = e); t = 0 } catch (e) { } } t && this.empty().append(e) }, null, e, arguments.length) }, replaceWith: function () { var n = []; return Be(this, arguments, function (e) { var t = this.parentNode; ce.inArray(this, n) < 0 && (ce.cleanData(Ee(this)), t && t.replaceChild(e, this)) }, n) } }), ce.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function (e, a) { ce.fn[e] = function (e) { for (var t, n = [], r = ce(e), i = r.length - 1, o = 0; o <= i; o++)t = o === i ? this : this.clone(!0), ce(r[o])[a](t), s.apply(n, t.get()); return this.pushStack(n) } }); var $e = new RegExp("^(" + Q + ")(?!px)[a-z%]+$", "i"), _e = /^--/, ze = function (e) { var t = e.ownerDocument.defaultView; return t && t.opener || (t = ie), t.getComputedStyle(e) }, Ue = function (e, t, n) { var r, i, o = {}; for (i in t) o[i] = e.style[i], e.style[i] = t[i]; for (i in r = n.call(e), t) e.style[i] = o[i]; return r }, Xe = new RegExp(G.join("|"), "i"); function Ve(e, t, n) { var r, i, o, a, s = _e.test(t), u = e.style; return (n = n || ze(e)) && (a = n.getPropertyValue(t) || n[t], s && a && (a = a.replace(ve, "$1") || void 0), "" !== a || J(e) || (a = ce.style(e, t)), !le.pixelBoxStyles() && $e.test(a) && Xe.test(t) && (r = u.width, i = u.minWidth, o = u.maxWidth, u.minWidth = u.maxWidth = u.width = a, a = n.width, u.width = r, u.minWidth = i, u.maxWidth = o)), void 0 !== a ? a + "" : a } function Qe(e, t) { return { get: function () { if (!e()) return (this.get = t).apply(this, arguments); delete this.get } } } !function () { function e() { if (l) { u.style.cssText = "position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0", l.style.cssText = "position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%", K.appendChild(u).appendChild(l); var e = ie.getComputedStyle(l); n = "1%" !== e.top, s = 12 === t(e.marginLeft), l.style.right = "60%", o = 36 === t(e.right), r = 36 === t(e.width), l.style.position = "absolute", i = 12 === t(l.offsetWidth / 3), K.removeChild(u), l = null } } function t(e) { return Math.round(parseFloat(e)) } var n, r, i, o, a, s, u = m.createElement("div"), l = m.createElement("div"); l.style && (l.style.backgroundClip = "content-box", l.cloneNode(!0).style.backgroundClip = "", le.clearCloneStyle = "content-box" === l.style.backgroundClip, ce.extend(le, { boxSizingReliable: function () { return e(), r }, pixelBoxStyles: function () { return e(), o }, pixelPosition: function () { return e(), n }, reliableMarginLeft: function () { return e(), s }, scrollboxSize: function () { return e(), i }, reliableTrDimensions: function () { var e, t, n, r; return null == a && (e = m.createElement("table"), t = m.createElement("tr"), n = m.createElement("div"), e.style.cssText = "position:absolute;left:-11111px;border-collapse:separate", t.style.cssText = "box-sizing:content-box;border:1px solid", t.style.height = "1px", n.style.height = "9px", n.style.display = "block", K.appendChild(e).appendChild(t).appendChild(n), r = ie.getComputedStyle(t), a = parseInt(r.height, 10) + parseInt(r.borderTopWidth, 10) + parseInt(r.borderBottomWidth, 10) === t.offsetHeight, K.removeChild(e)), a } })) }(); var Ye = ["Webkit", "Moz", "ms"], Ge = m.createElement("div").style, Ke = {}; function Je(e) { var t = ce.cssProps[e] || Ke[e]; return t || (e in Ge ? e : Ke[e] = function (e) { var t = e[0].toUpperCase() + e.slice(1), n = Ye.length; while (n--) if ((e = Ye[n] + t) in Ge) return e }(e) || e) } var Ze, et, tt = /^(none|table(?!-c[ea]).+)/, nt = { position: "absolute", visibility: "hidden", display: "block" }, rt = { letterSpacing: "0", fontWeight: "400" }; function it(e, t, n) { var r = Y.exec(t); return r ? Math.max(0, r[2] - (n || 0)) + (r[3] || "px") : t } function ot(e, t, n, r, i, o) { var a = "width" === t ? 1 : 0, s = 0, u = 0, l = 0; if (n === (r ? "border" : "content")) return 0; for (; a < 4; a += 2)"margin" === n && (l += ce.css(e, n + G[a], !0, i)), r ? ("content" === n && (u -= ce.css(e, "padding" + G[a], !0, i)), "margin" !== n && (u -= ce.css(e, "border" + G[a] + "Width", !0, i))) : (u += ce.css(e, "padding" + G[a], !0, i), "padding" !== n ? u += ce.css(e, "border" + G[a] + "Width", !0, i) : s += ce.css(e, "border" + G[a] + "Width", !0, i)); return !r && 0 <= o && (u += Math.max(0, Math.ceil(e["offset" + t[0].toUpperCase() + t.slice(1)] - o - u - s - .5)) || 0), u + l } function at(e, t, n) { var r = ze(e), i = (!le.boxSizingReliable() || n) && "border-box" === ce.css(e, "boxSizing", !1, r), o = i, a = Ve(e, t, r), s = "offset" + t[0].toUpperCase() + t.slice(1); if ($e.test(a)) { if (!n) return a; a = "auto" } return (!le.boxSizingReliable() && i || !le.reliableTrDimensions() && fe(e, "tr") || "auto" === a || !parseFloat(a) && "inline" === ce.css(e, "display", !1, r)) && e.getClientRects().length && (i = "border-box" === ce.css(e, "boxSizing", !1, r), (o = s in e) && (a = e[s])), (a = parseFloat(a) || 0) + ot(e, t, n || (i ? "border" : "content"), o, r, a) + "px" } ce.extend({ cssHooks: { opacity: { get: function (e, t) { if (t) { var n = Ve(e, "opacity"); return "" === n ? "1" : n } } } }, cssNumber: { animationIterationCount: !0, aspectRatio: !0, borderImageSlice: !0, columnCount: !0, flexGrow: !0, flexShrink: !0, fontWeight: !0, gridArea: !0, gridColumn: !0, gridColumnEnd: !0, gridColumnStart: !0, gridRow: !0, gridRowEnd: !0, gridRowStart: !0, lineHeight: !0, opacity: !0, order: !0, orphans: !0, scale: !0, widows: !0, zIndex: !0, zoom: !0, fillOpacity: !0, floodOpacity: !0, stopOpacity: !0, strokeMiterlimit: !0, strokeOpacity: !0 }, cssProps: {}, style: function (e, t, n, r) { if (e && 3 !== e.nodeType && 8 !== e.nodeType && e.style) { var i, o, a, s = B(t), u = _e.test(t), l = e.style; if (u || (t = Je(s)), a = ce.cssHooks[t] || ce.cssHooks[s], void 0 === n) return a && "get" in a && void 0 !== (i = a.get(e, !1, r)) ? i : l[t]; "string" === (o = typeof n) && (i = Y.exec(n)) && i[1] && (n = function (e, t, n, r) { var i, o, a = 20, s = r ? function () { return r.cur() } : function () { return ce.css(e, t, "") }, u = s(), l = n && n[3] || (ce.cssNumber[t] ? "" : "px"), c = e.nodeType && (ce.cssNumber[t] || "px" !== l && +u) && Y.exec(ce.css(e, t)); if (c && c[3] !== l) { u /= 2, l = l || c[3], c = +u || 1; while (a--) ce.style(e, t, c + l), (1 - o) * (1 - (o = s() / u || .5)) <= 0 && (a = 0), c /= o; c *= 2, ce.style(e, t, c + l), n = n || [] } return n && (c = +c || +u || 0, i = n[1] ? c + (n[1] + 1) * n[2] : +n[2], r && (r.unit = l, r.start = c, r.end = i)), i }(e, t, i), o = "number"), null != n && n == n && ("number" !== o || u || (n += i && i[3] || (ce.cssNumber[s] ? "" : "px")), le.clearCloneStyle || "" !== n || 0 !== t.indexOf("background") || (l[t] = "inherit"), a && "set" in a && void 0 === (n = a.set(e, n, r)) || (u ? l.setProperty(t, n) : l[t] = n)) } }, css: function (e, t, n, r) { var i, o, a, s = B(t); return _e.test(t) || (t = Je(s)), (a = ce.cssHooks[t] || ce.cssHooks[s]) && "get" in a && (i = a.get(e, !0, n)), void 0 === i && (i = Ve(e, t, r)), "normal" === i && t in rt && (i = rt[t]), "" === n || n ? (o = parseFloat(i), !0 === n || isFinite(o) ? o || 0 : i) : i } }), ce.each(["height", "width"], function (e, u) { ce.cssHooks[u] = { get: function (e, t, n) { if (t) return !tt.test(ce.css(e, "display")) || e.getClientRects().length && e.getBoundingClientRect().width ? at(e, u, n) : Ue(e, nt, function () { return at(e, u, n) }) }, set: function (e, t, n) { var r, i = ze(e), o = !le.scrollboxSize() && "absolute" === i.position, a = (o || n) && "border-box" === ce.css(e, "boxSizing", !1, i), s = n ? ot(e, u, n, a, i) : 0; return a && o && (s -= Math.ceil(e["offset" + u[0].toUpperCase() + u.slice(1)] - parseFloat(i[u]) - ot(e, u, "border", !1, i) - .5)), s && (r = Y.exec(t)) && "px" !== (r[3] || "px") && (e.style[u] = t, t = ce.css(e, u)), it(0, t, s) } } }), ce.cssHooks.marginLeft = Qe(le.reliableMarginLeft, function (e, t) { if (t) return (parseFloat(Ve(e, "marginLeft")) || e.getBoundingClientRect().left - Ue(e, { marginLeft: 0 }, function () { return e.getBoundingClientRect().left })) + "px" }), ce.each({ margin: "", padding: "", border: "Width" }, function (i, o) { ce.cssHooks[i + o] = { expand: function (e) { for (var t = 0, n = {}, r = "string" == typeof e ? e.split(" ") : [e]; t < 4; t++)n[i + G[t] + o] = r[t] || r[t - 2] || r[0]; return n } }, "margin" !== i && (ce.cssHooks[i + o].set = it) }), ce.fn.extend({ css: function (e, t) { return R(this, function (e, t, n) { var r, i, o = {}, a = 0; if (Array.isArray(t)) { for (r = ze(e), i = t.length; a < i; a++)o[t[a]] = ce.css(e, t[a], !1, r); return o } return void 0 !== n ? ce.style(e, t, n) : ce.css(e, t) }, e, t, 1 < arguments.length) } }), ce.fn.delay = function (r, e) { return r = ce.fx && ce.fx.speeds[r] || r, e = e || "fx", this.queue(e, function (e, t) { var n = ie.setTimeout(e, r); t.stop = function () { ie.clearTimeout(n) } }) }, Ze = m.createElement("input"), et = m.createElement("select").appendChild(m.createElement("option")), Ze.type = "checkbox", le.checkOn = "" !== Ze.value, le.optSelected = et.selected, (Ze = m.createElement("input")).value = "t", Ze.type = "radio", le.radioValue = "t" === Ze.value; var st, ut = ce.expr.attrHandle; ce.fn.extend({ attr: function (e, t) { return R(this, ce.attr, e, t, 1 < arguments.length) }, removeAttr: function (e) { return this.each(function () { ce.removeAttr(this, e) }) } }), ce.extend({ attr: function (e, t, n) { var r, i, o = e.nodeType; if (3 !== o && 8 !== o && 2 !== o) return "undefined" == typeof e.getAttribute ? ce.prop(e, t, n) : (1 === o && ce.isXMLDoc(e) || (i = ce.attrHooks[t.toLowerCase()] || (ce.expr.match.bool.test(t) ? st : void 0)), void 0 !== n ? null === n ? void ce.removeAttr(e, t) : i && "set" in i && void 0 !== (r = i.set(e, n, t)) ? r : (e.setAttribute(t, n + ""), n) : i && "get" in i && null !== (r = i.get(e, t)) ? r : null == (r = ce.find.attr(e, t)) ? void 0 : r) }, attrHooks: { type: { set: function (e, t) { if (!le.radioValue && "radio" === t && fe(e, "input")) { var n = e.value; return e.setAttribute("type", t), n && (e.value = n), t } } } }, removeAttr: function (e, t) { var n, r = 0, i = t && t.match(N); if (i && 1 === e.nodeType) while (n = i[r++]) e.removeAttribute(n) } }), st = { set: function (e, t, n) { return !1 === t ? ce.removeAttr(e, n) : e.setAttribute(n, n), n } }, ce.each(ce.expr.match.bool.source.match(/\w+/g), function (e, t) { var a = ut[t] || ce.find.attr; ut[t] = function (e, t, n) { var r, i, o = t.toLowerCase(); return n || (i = ut[o], ut[o] = r, r = null != a(e, t, n) ? o : null, ut[o] = i), r } }); var lt = /^(?:input|select|textarea|button)$/i, ct = /^(?:a|area)$/i; function ft(e) { return (e.match(N) || []).join(" ") } function dt(e) { return e.getAttribute && e.getAttribute("class") || "" } function pt(e) { return Array.isArray(e) ? e : "string" == typeof e && e.match(N) || [] } ce.fn.extend({ prop: function (e, t) { return R(this, ce.prop, e, t, 1 < arguments.length) }, removeProp: function (e) { return this.each(function () { delete this[ce.propFix[e] || e] }) } }), ce.extend({ prop: function (e, t, n) { var r, i, o = e.nodeType; if (3 !== o && 8 !== o && 2 !== o) return 1 === o && ce.isXMLDoc(e) || (t = ce.propFix[t] || t, i = ce.propHooks[t]), void 0 !== n ? i && "set" in i && void 0 !== (r = i.set(e, n, t)) ? r : e[t] = n : i && "get" in i && null !== (r = i.get(e, t)) ? r : e[t] }, propHooks: { tabIndex: { get: function (e) { var t = ce.find.attr(e, "tabindex"); return t ? parseInt(t, 10) : lt.test(e.nodeName) || ct.test(e.nodeName) && e.href ? 0 : -1 } } }, propFix: { "for": "htmlFor", "class": "className" } }), le.optSelected || (ce.propHooks.selected = { get: function (e) { var t = e.parentNode; return t && t.parentNode && t.parentNode.selectedIndex, null }, set: function (e) { var t = e.parentNode; t && (t.selectedIndex, t.parentNode && t.parentNode.selectedIndex) } }), ce.each(["tabIndex", "readOnly", "maxLength", "cellSpacing", "cellPadding", "rowSpan", "colSpan", "useMap", "frameBorder", "contentEditable"], function () { ce.propFix[this.toLowerCase()] = this }), ce.fn.extend({ addClass: function (t) { var e, n, r, i, o, a; return v(t) ? this.each(function (e) { ce(this).addClass(t.call(this, e, dt(this))) }) : (e = pt(t)).length ? this.each(function () { if (r = dt(this), n = 1 === this.nodeType && " " + ft(r) + " ") { for (o = 0; o < e.length; o++)i = e[o], n.indexOf(" " + i + " ") < 0 && (n += i + " "); a = ft(n), r !== a && this.setAttribute("class", a) } }) : this }, removeClass: function (t) { var e, n, r, i, o, a; return v(t) ? this.each(function (e) { ce(this).removeClass(t.call(this, e, dt(this))) }) : arguments.length ? (e = pt(t)).length ? this.each(function () { if (r = dt(this), n = 1 === this.nodeType && " " + ft(r) + " ") { for (o = 0; o < e.length; o++) { i = e[o]; while (-1 < n.indexOf(" " + i + " ")) n = n.replace(" " + i + " ", " ") } a = ft(n), r !== a && this.setAttribute("class", a) } }) : this : this.attr("class", "") }, toggleClass: function (t, n) { var e, r, i, o, a = typeof t, s = "string" === a || Array.isArray(t); return v(t) ? this.each(function (e) { ce(this).toggleClass(t.call(this, e, dt(this), n), n) }) : "boolean" == typeof n && s ? n ? this.addClass(t) : this.removeClass(t) : (e = pt(t), this.each(function () { if (s) for (o = ce(this), i = 0; i < e.length; i++)r = e[i], o.hasClass(r) ? o.removeClass(r) : o.addClass(r); else void 0 !== t && "boolean" !== a || ((r = dt(this)) && _.set(this, "__className__", r), this.setAttribute && this.setAttribute("class", r || !1 === t ? "" : _.get(this, "__className__") || "")) })) }, hasClass: function (e) { var t, n, r = 0; t = " " + e + " "; while (n = this[r++]) if (1 === n.nodeType && -1 < (" " + ft(dt(n)) + " ").indexOf(t)) return !0; return !1 } }); var ht = /\r/g; ce.fn.extend({ val: function (n) { var r, e, i, t = this[0]; return arguments.length ? (i = v(n), this.each(function (e) { var t; 1 === this.nodeType && (null == (t = i ? n.call(this, e, ce(this).val()) : n) ? t = "" : "number" == typeof t ? t += "" : Array.isArray(t) && (t = ce.map(t, function (e) { return null == e ? "" : e + "" })), (r = ce.valHooks[this.type] || ce.valHooks[this.nodeName.toLowerCase()]) && "set" in r && void 0 !== r.set(this, t, "value") || (this.value = t)) })) : t ? (r = ce.valHooks[t.type] || ce.valHooks[t.nodeName.toLowerCase()]) && "get" in r && void 0 !== (e = r.get(t, "value")) ? e : "string" == typeof (e = t.value) ? e.replace(ht, "") : null == e ? "" : e : void 0 } }), ce.extend({ valHooks: { option: { get: function (e) { var t = ce.find.attr(e, "value"); return null != t ? t : ft(ce.text(e)) } }, select: { get: function (e) { var t, n, r, i = e.options, o = e.selectedIndex, a = "select-one" === e.type, s = a ? null : [], u = a ? o + 1 : i.length; for (r = o < 0 ? u : a ? o : 0; r < u; r++)if (((n = i[r]).selected || r === o) && !n.disabled && (!n.parentNode.disabled || !fe(n.parentNode, "optgroup"))) { if (t = ce(n).val(), a) return t; s.push(t) } return s }, set: function (e, t) { var n, r, i = e.options, o = ce.makeArray(t), a = i.length; while (a--) ((r = i[a]).selected = -1 < ce.inArray(ce.valHooks.option.get(r), o)) && (n = !0); return n || (e.selectedIndex = -1), o } } } }), ce.each(["radio", "checkbox"], function () { ce.valHooks[this] = { set: function (e, t) { if (Array.isArray(t)) return e.checked = -1 < ce.inArray(ce(e).val(), t) } }, le.checkOn || (ce.valHooks[this].get = function (e) { return null === e.getAttribute("value") ? "on" : e.value }) }), ce.parseXML = function (e) { var t, n; if (!e || "string" != typeof e) return null; try { t = (new ie.DOMParser).parseFromString(e, "text/xml") } catch (e) { } return n = t && t.getElementsByTagName("parsererror")[0], t && !n || ce.error("Invalid XML: " + (n ? ce.map(n.childNodes, function (e) { return e.textContent }).join("\n") : e)), t }; var gt = /^(?:focusinfocus|focusoutblur)$/, vt = function (e) { e.stopPropagation() }; ce.extend(ce.event, { trigger: function (e, t, n, r) { var i, o, a, s, u, l, c, f, d = [n || m], p = ue.call(e, "type") ? e.type : e, h = ue.call(e, "namespace") ? e.namespace.split(".") : []; if (o = f = a = n = n || m, 3 !== n.nodeType && 8 !== n.nodeType && !gt.test(p + ce.event.triggered) && (-1 < p.indexOf(".") && (p = (h = p.split(".")).shift(), h.sort()), u = p.indexOf(":") < 0 && "on" + p, (e = e[ce.expando] ? e : new ce.Event(p, "object" == typeof e && e)).isTrigger = r ? 2 : 3, e.namespace = h.join("."), e.rnamespace = e.namespace ? new RegExp("(^|\\.)" + h.join("\\.(?:.*\\.|)") + "(\\.|$)") : null, e.result = void 0, e.target || (e.target = n), t = null == t ? [e] : ce.makeArray(t, [e]), c = ce.event.special[p] || {}, r || !c.trigger || !1 !== c.trigger.apply(n, t))) { if (!r && !c.noBubble && !y(n)) { for (s = c.delegateType || p, gt.test(s + p) || (o = o.parentNode); o; o = o.parentNode)d.push(o), a = o; a === (n.ownerDocument || m) && d.push(a.defaultView || a.parentWindow || ie) } i = 0; while ((o = d[i++]) && !e.isPropagationStopped()) f = o, e.type = 1 < i ? s : c.bindType || p, (l = (_.get(o, "events") || Object.create(null))[e.type] && _.get(o, "handle")) && l.apply(o, t), (l = u && o[u]) && l.apply && F(o) && (e.result = l.apply(o, t), !1 === e.result && e.preventDefault()); return e.type = p, r || e.isDefaultPrevented() || c._default && !1 !== c._default.apply(d.pop(), t) || !F(n) || u && v(n[p]) && !y(n) && ((a = n[u]) && (n[u] = null), ce.event.triggered = p, e.isPropagationStopped() && f.addEventListener(p, vt), n[p](), e.isPropagationStopped() && f.removeEventListener(p, vt), ce.event.triggered = void 0, a && (n[u] = a)), e.result } }, simulate: function (e, t, n) { var r = ce.extend(new ce.Event, n, { type: e, isSimulated: !0 }); ce.event.trigger(r, null, t) } }), ce.fn.extend({ trigger: function (e, t) { return this.each(function () { ce.event.trigger(e, t, this) }) }, triggerHandler: function (e, t) { var n = this[0]; if (n) return ce.event.trigger(e, t, n, !0) } }); var yt, mt = /\[\]$/, bt = /\r?\n/g, xt = /^(?:submit|button|image|reset|file)$/i, wt = /^(?:input|select|textarea|keygen)/i; function Ct(n, e, r, i) { var t; if (Array.isArray(e)) ce.each(e, function (e, t) { r || mt.test(n) ? i(n, t) : Ct(n + "[" + ("object" == typeof t && null != t ? e : "") + "]", t, r, i) }); else if (r || "object" !== x(e)) i(n, e); else for (t in e) Ct(n + "[" + t + "]", e[t], r, i) } ce.param = function (e, t) { var n, r = [], i = function (e, t) { var n = v(t) ? t() : t; r[r.length] = encodeURIComponent(e) + "=" + encodeURIComponent(null == n ? "" : n) }; if (null == e) return ""; if (Array.isArray(e) || e.jquery && !ce.isPlainObject(e)) ce.each(e, function () { i(this.name, this.value) }); else for (n in e) Ct(n, e[n], t, i); return r.join("&") }, ce.fn.extend({ serialize: function () { return ce.param(this.serializeArray()) }, serializeArray: function () { return this.map(function () { var e = ce.prop(this, "elements"); return e ? ce.makeArray(e) : this }).filter(function () { var e = this.type; return this.name && !ce(this).is(":disabled") && wt.test(this.nodeName) && !xt.test(e) && (this.checked || !xe.test(e)) }).map(function (e, t) { var n = ce(this).val(); return null == n ? null : Array.isArray(n) ? ce.map(n, function (e) { return { name: t.name, value: e.replace(bt, "\r\n") } }) : { name: t.name, value: n.replace(bt, "\r\n") } }).get() } }), ce.fn.extend({ wrapAll: function (e) { var t; return this[0] && (v(e) && (e = e.call(this[0])), t = ce(e, this[0].ownerDocument).eq(0).clone(!0), this[0].parentNode && t.insertBefore(this[0]), t.map(function () { var e = this; while (e.firstElementChild) e = e.firstElementChild; return e }).append(this)), this }, wrapInner: function (n) { return v(n) ? this.each(function (e) { ce(this).wrapInner(n.call(this, e)) }) : this.each(function () { var e = ce(this), t = e.contents(); t.length ? t.wrapAll(n) : e.append(n) }) }, wrap: function (t) { var n = v(t); return this.each(function (e) { ce(this).wrapAll(n ? t.call(this, e) : t) }) }, unwrap: function (e) { return this.parent(e).not("body").each(function () { ce(this).replaceWith(this.childNodes) }), this } }), ce.expr.pseudos.hidden = function (e) { return !ce.expr.pseudos.visible(e) }, ce.expr.pseudos.visible = function (e) { return !!(e.offsetWidth || e.offsetHeight || e.getClientRects().length) }, le.createHTMLDocument = ((yt = m.implementation.createHTMLDocument("").body).innerHTML = "<form></form><form></form>", 2 === yt.childNodes.length), ce.parseHTML = function (e, t, n) { return "string" != typeof e ? [] : ("boolean" == typeof t && (n = t, t = !1), t || (le.createHTMLDocument ? ((r = (t = m.implementation.createHTMLDocument("")).createElement("base")).href = m.location.href, t.head.appendChild(r)) : t = m), o = !n && [], (i = C.exec(e)) ? [t.createElement(i[1])] : (i = Ae([e], t, o), o && o.length && ce(o).remove(), ce.merge([], i.childNodes))); var r, i, o }, ce.offset = { setOffset: function (e, t, n) { var r, i, o, a, s, u, l = ce.css(e, "position"), c = ce(e), f = {}; "static" === l && (e.style.position = "relative"), s = c.offset(), o = ce.css(e, "top"), u = ce.css(e, "left"), ("absolute" === l || "fixed" === l) && -1 < (o + u).indexOf("auto") ? (a = (r = c.position()).top, i = r.left) : (a = parseFloat(o) || 0, i = parseFloat(u) || 0), v(t) && (t = t.call(e, n, ce.extend({}, s))), null != t.top && (f.top = t.top - s.top + a), null != t.left && (f.left = t.left - s.left + i), "using" in t ? t.using.call(e, f) : c.css(f) } }, ce.fn.extend({ offset: function (t) { if (arguments.length) return void 0 === t ? this : this.each(function (e) { ce.offset.setOffset(this, t, e) }); var e, n, r = this[0]; return r ? r.getClientRects().length ? (e = r.getBoundingClientRect(), n = r.ownerDocument.defaultView, { top: e.top + n.pageYOffset, left: e.left + n.pageXOffset }) : { top: 0, left: 0 } : void 0 }, position: function () { if (this[0]) { var e, t, n, r = this[0], i = { top: 0, left: 0 }; if ("fixed" === ce.css(r, "position")) t = r.getBoundingClientRect(); else { t = this.offset(), n = r.ownerDocument, e = r.offsetParent || n.documentElement; while (e && (e === n.body || e === n.documentElement) && "static" === ce.css(e, "position")) e = e.parentNode; e && e !== r && 1 === e.nodeType && ((i = ce(e).offset()).top += ce.css(e, "borderTopWidth", !0), i.left += ce.css(e, "borderLeftWidth", !0)) } return { top: t.top - i.top - ce.css(r, "marginTop", !0), left: t.left - i.left - ce.css(r, "marginLeft", !0) } } }, offsetParent: function () { return this.map(function () { var e = this.offsetParent; while (e && "static" === ce.css(e, "position")) e = e.offsetParent; return e || K }) } }), ce.each({ scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function (t, i) { var o = "pageYOffset" === i; ce.fn[t] = function (e) { return R(this, function (e, t, n) { var r; if (y(e) ? r = e : 9 === e.nodeType && (r = e.defaultView), void 0 === n) return r ? r[i] : e[t]; r ? r.scrollTo(o ? r.pageXOffset : n, o ? n : r.pageYOffset) : e[t] = n }, t, e, arguments.length) } }), ce.each(["top", "left"], function (e, n) { ce.cssHooks[n] = Qe(le.pixelPosition, function (e, t) { if (t) return t = Ve(e, n), $e.test(t) ? ce(e).position()[n] + "px" : t }) }), ce.each({ Height: "height", Width: "width" }, function (a, s) { ce.each({ padding: "inner" + a, content: s, "": "outer" + a }, function (r, o) { ce.fn[o] = function (e, t) { var n = arguments.length && (r || "boolean" != typeof e), i = r || (!0 === e || !0 === t ? "margin" : "border"); return R(this, function (e, t, n) { var r; return y(e) ? 0 === o.indexOf("outer") ? e["inner" + a] : e.document.documentElement["client" + a] : 9 === e.nodeType ? (r = e.documentElement, Math.max(e.body["scroll" + a], r["scroll" + a], e.body["offset" + a], r["offset" + a], r["client" + a])) : void 0 === n ? ce.css(e, t, i) : ce.style(e, t, n, i) }, s, n ? e : void 0, n) } }) }), ce.fn.extend({ bind: function (e, t, n) { return this.on(e, null, t, n) }, unbind: function (e, t) { return this.off(e, null, t) }, delegate: function (e, t, n, r) { return this.on(t, e, n, r) }, undelegate: function (e, t, n) { return 1 === arguments.length ? this.off(e, "**") : this.off(t, e || "**", n) }, hover: function (e, t) { return this.on("mouseenter", e).on("mouseleave", t || e) } }), ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "), function (e, n) { ce.fn[n] = function (e, t) { return 0 < arguments.length ? this.on(n, null, e, t) : this.trigger(n) } }); var Tt = /^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g; ce.proxy = function (e, t) { var n, r, i; if ("string" == typeof t && (n = e[t], t = e, e = n), v(e)) return r = ae.call(arguments, 2), (i = function () { return e.apply(t || this, r.concat(ae.call(arguments))) }).guid = e.guid = e.guid || ce.guid++, i }, ce.holdReady = function (e) { e ? ce.readyWait++ : ce.ready(!0) }, ce.isArray = Array.isArray, ce.parseJSON = JSON.parse, ce.nodeName = fe, ce.isFunction = v, ce.isWindow = y, ce.camelCase = B, ce.type = x, ce.now = Date.now, ce.isNumeric = function (e) { var t = ce.type(e); return ("number" === t || "string" === t) && !isNaN(e - parseFloat(e)) }, ce.trim = function (e) { return null == e ? "" : (e + "").replace(Tt, "$1") }, "function" == typeof define && define.amd && define("jquery", [], function () { return ce }); var Et = ie.jQuery, kt = ie.$; return ce.noConflict = function (e) { return ie.$ === ce && (ie.$ = kt), e && ie.jQuery === ce && (ie.jQuery = Et), ce }, "undefined" == typeof e && (ie.jQuery = ie.$ = ce), ce });
/*! jquery.cookie v1.4.1 | MIT */
!(function (a) {
"function" == typeof define && define.amd
? define(["jquery"], a)
: "object" == typeof exports
? a(require("jquery"))
: a(jQuery);
})(function (a) {
function b(a) {
return h.raw ? a : encodeURIComponent(a);
}
function c(a) {
return h.raw ? a : decodeURIComponent(a);
}
function d(a) {
return b(h.json ? JSON.stringify(a) : String(a));
}
function e(a) {
0 === a.indexOf('"') &&
(a = a.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\"));
try {
return (
(a = decodeURIComponent(a.replace(g, " "))), h.json ? JSON.parse(a) : a
);
} catch (b) {}
}
function f(b, c) {
var d = h.raw ? b : e(b);
return a.isFunction(c) ? c(d) : d;
}
var g = /\+/g,
h = (a.cookie = function (e, g, i) {
if (void 0 !== g && !a.isFunction(g)) {
if (((i = a.extend({}, h.defaults, i)), "number" == typeof i.expires)) {
var j = i.expires,
k = (i.expires = new Date());
k.setTime(+k + 864e5 * j);
}
return (document.cookie = [
b(e),
"=",
d(g),
i.expires ? "; expires=" + i.expires.toUTCString() : "",
i.path ? "; path=" + i.path : "",
i.domain ? "; domain=" + i.domain : "",
i.secure ? "; secure" : "",
].join(""));
}
for (
var l = e ? void 0 : {},
m = document.cookie ? document.cookie.split("; ") : [],
n = 0,
o = m.length;
o > n;
n++
) {
var p = m[n].split("="),
q = c(p.shift()),
r = p.join("=");
if (e && e === q) {
l = f(r, g);
break;
}
e || void 0 === (r = f(r)) || (l[q] = r);
}
return l;
});
(h.defaults = {}),
(a.removeCookie = function (b, c) {
return void 0 === a.cookie(b)
? !1
: (a.cookie(b, "", a.extend({}, c, { expires: -1 })), !a.cookie(b));
});
});
/*! css.js 27-02-2018 */
!(function (e) {
"use strict";
var t = function () {
(this.cssImportStatements = []),
(this.cssKeyframeStatements = []),
(this.cssRegex = new RegExp("([\\s\\S]*?){([\\s\\S]*?)}", "gi")),
(this.cssMediaQueryRegex = "((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})"),
(this.cssKeyframeRegex =
"((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})"),
(this.combinedCSSRegex =
"((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})"),
(this.cssCommentsRegex = "(\\/\\*[\\s\\S]*?\\*\\/)"),
(this.cssImportStatementRegex = new RegExp("@import .*?;", "gi"));
};
(t.prototype.stripComments = function (e) {
var t = new RegExp(this.cssCommentsRegex, "gi");
return e.replace(t, "");
}),
(t.prototype.parseCSS = function (e) {
if (void 0 === e) return [];
for (var t = []; ; ) {
var s = this.cssImportStatementRegex.exec(e);
if (null === s) break;
this.cssImportStatements.push(s[0]),
t.push({ selector: "@imports", type: "imports", styles: s[0] });
}
e = e.replace(this.cssImportStatementRegex, "");
for (
var r, i = new RegExp(this.cssKeyframeRegex, "gi");
null !== (r = i.exec(e));
)
t.push({ selector: "@keyframes", type: "keyframes", styles: r[0] });
e = e.replace(i, "");
for (
var n = new RegExp(this.combinedCSSRegex, "gi");
null !== (r = n.exec(e));
) {
var o = "";
o =
void 0 === r[2]
? r[5].split("\r\n").join("\n").trim()
: r[2].split("\r\n").join("\n").trim();
var l = new RegExp(this.cssCommentsRegex, "gi"),
p = l.exec(o);
if (
(null !== p && (o = o.replace(l, "").trim()),
-1 !== (o = o.replace(/\n+/, "\n")).indexOf("@media"))
) {
var a = {
selector: o,
type: "media",
subStyles: this.parseCSS(r[3] + "\n}"),
};
null !== p && (a.comments = p[0]), t.push(a);
} else {
var c = { selector: o, rules: this.parseRules(r[6]) };
"@font-face" === o && (c.type = "font-face"),
null !== p && (c.comments = p[0]),
t.push(c);
}
}
return t;
}),
(t.prototype.parseRules = function (e) {
var t = [];
e = (e = e.split("\r\n").join("\n")).split(";");
for (var s = 0; s < e.length; s++) {
var r = e[s];
if (-1 !== (r = r.trim()).indexOf(":")) {
var i = (r = r.split(":"))[0].trim(),
n = r.slice(1).join(":").trim();
if (i.length < 1 || n.length < 1) continue;
t.push({ directive: i, value: n });
} else
"base64," === r.trim().substr(0, 7)
? (t[t.length - 1].value += r.trim())
: r.length > 0 &&
t.push({ directive: "", value: r, defective: !0 });
}
return t;
}),
(t.prototype.findCorrespondingRule = function (e, t, s) {
void 0 === s && (s = !1);
for (
var r = !1, i = 0;
i < e.length &&
(e[i].directive !== t || ((r = e[i]), s !== e[i].value));
i++
);
return r;
}),
(t.prototype.findBySelector = function (e, t, s) {
void 0 === s && (s = !1);
for (var r = [], i = 0; i < e.length; i++)
!1 === s
? e[i].selector === t && r.push(e[i])
: -1 !== e[i].selector.indexOf(t) && r.push(e[i]);
if ("@imports" === t || r.length < 2) return r;
var n = r[0];
for (i = 1; i < r.length; i++) this.intelligentCSSPush([n], r[i]);
return [n];
}),
(t.prototype.deleteBySelector = function (e, t) {
for (var s = [], r = 0; r < e.length; r++)
e[r].selector !== t && s.push(e[r]);
return s;
}),
(t.prototype.compressCSS = function (e) {
for (var t = [], s = {}, r = 0; r < e.length; r++) {
var i = e[r];
if (!0 !== s[i.selector]) {
var n = this.findBySelector(e, i.selector);
0 !== n.length && ((t = t.concat(n)), (s[i.selector] = !0));
}
}
return t;
}),
(t.prototype.cssDiff = function (e, t) {
if (e.selector !== t.selector) return !1;
if ("media" === e.type || "media" === t.type) return !1;
for (
var s, r, i = { selector: e.selector, rules: [] }, n = 0;
n < e.rules.length;
n++
)
(s = e.rules[n]),
!1 === (r = this.findCorrespondingRule(t.rules, s.directive, s.value))
? i.rules.push(s)
: s.value !== r.value && i.rules.push(s);
for (var o = 0; o < t.rules.length; o++)
(r = t.rules[o]),
!1 === (s = this.findCorrespondingRule(e.rules, r.directive)) &&
((r.type = "DELETED"), i.rules.push(r));
return 0 !== i.rules.length && i;
}),
(t.prototype.intelligentMerge = function (e, t, s) {
void 0 === s && (s = !1);
for (var r = 0; r < t.length; r++) this.intelligentCSSPush(e, t[r], s);
for (r = 0; r < e.length; r++) {
var i = e[r];
"media" !== i.type &&
"keyframes" !== i.type &&
(i.rules = this.compactRules(i.rules));
}
}),
(t.prototype.intelligentCSSPush = function (e, t, s) {
var r = t.selector,
i = !1;
if ((void 0 === s && (s = !1), !1 === s)) {
for (var n = 0; n < e.length; n++)
if (e[n].selector === r) {
i = e[n];
break;
}
} else
for (var o = e.length - 1; o > -1; o--)
if (e[o].selector === r) {
i = e[o];
break;
}
if (!1 === i) e.push(t);
else if ("media" !== t.type)
for (var l = 0; l < t.rules.length; l++) {
var p = t.rules[l],
a = this.findCorrespondingRule(i.rules, p.directive);
!1 === a
? i.rules.push(p)
: "DELETED" === p.type
? (a.type = "DELETED")
: (a.value = p.value);
}
else i.subStyles = i.subStyles.concat(t.subStyles);
}),
(t.prototype.compactRules = function (e) {
for (var t = [], s = 0; s < e.length; s++)
"DELETED" !== e[s].type && t.push(e[s]);
return t;
}),
(t.prototype.getCSSForEditor = function (e, t) {
void 0 === t && (t = 0);
var s = "";
void 0 === e && (e = this.css);
for (var r = 0; r < e.length; r++)
"imports" === e[r].type && (s += e[r].styles + "\n\n");
for (r = 0; r < e.length; r++) {
var i = e[r];
if (void 0 !== i.selector) {
var n = "";
void 0 !== i.comments && (n = i.comments + "\n"),
"media" === i.type
? ((s += n + i.selector + "{\n"),
(s += this.getCSSForEditor(i.subStyles, t + 1)),
(s += "}\n\n"))
: "keyframes" !== i.type &&
"imports" !== i.type &&
((s += this.getSpaces(t) + n + i.selector + " {\n"),
(s += this.getCSSOfRules(i.rules, t + 1)),
(s += this.getSpaces(t) + "}\n\n"));
}
}
for (r = 0; r < e.length; r++)
"keyframes" === e[r].type && (s += e[r].styles + "\n\n");
return s;
}),
(t.prototype.getImports = function (e) {
for (var t = [], s = 0; s < e.length; s++)
"imports" === e[s].type && t.push(e[s].styles);
return t;
}),
(t.prototype.getCSSOfRules = function (e, t) {
for (var s = "", r = 0; r < e.length; r++)
void 0 !== e[r] &&
(void 0 === e[r].defective
? (s +=
this.getSpaces(t) + e[r].directive + ": " + e[r].value + ";\n")
: (s += this.getSpaces(t) + e[r].value + ";\n"));
return s || "\n";
}),
(t.prototype.getSpaces = function (e) {
for (var t = "", s = 0; s < 4 * e; s++) t += " ";
return t;
}),
(t.prototype.applyNamespacing = function (e, t) {
var s = e,
r = "." + this.cssPreviewNamespace;
void 0 !== t && (r = t), "string" == typeof e && (s = this.parseCSS(e));
for (var i = 0; i < s.length; i++) {
var n = s[i];
if (
!(
n.selector.indexOf("@font-face") > -1 ||
n.selector.indexOf("keyframes") > -1 ||
n.selector.indexOf("@import") > -1 ||
n.selector.indexOf(".form-all") > -1 ||
n.selector.indexOf("#stage") > -1
)
)
if ("media" !== n.type) {
for (
var o = n.selector.split(","), l = [], p = 0;
p < o.length;
p++
)
-1 === o[p].indexOf(".supernova")
? l.push(r + " " + o[p])
: l.push(o[p]);
n.selector = l.join(",");
} else n.subStyles = this.applyNamespacing(n.subStyles, t);
}
return s;
}),
(t.prototype.clearNamespacing = function (e, t) {
void 0 === t && (t = !1);
var s = e,
r = "." + this.cssPreviewNamespace;
"string" == typeof e && (s = this.parseCSS(e));
for (var i = 0; i < s.length; i++) {
var n = s[i];
if ("media" !== n.type) {
for (var o = n.selector.split(","), l = [], p = 0; p < o.length; p++)
l.push(o[p].split(r + " ").join(""));
n.selector = l.join(",");
} else n.subStyles = this.clearNamespacing(n.subStyles, !0);
}
return !1 === t ? this.getCSSForEditor(s) : s;
}),
(t.prototype.createStyleElement = function (e, t, s) {
if (
(void 0 === s && (s = !1),
!1 === this.testMode &&
"nonamespace" !== s &&
(t = this.applyNamespacing(t)),
"string" != typeof t && (t = this.getCSSForEditor(t)),
!0 === s && (t = this.getCSSForEditor(this.parseCSS(t))),
!1 !== this.testMode)
)
return this.testMode("create style #" + e, t);
var r = document.getElementById(e);
r && r.parentNode.removeChild(r);
var i = document.head || document.getElementsByTagName("head")[0],
n = document.createElement("style");
(n.id = e),
(n.type = "text/css"),
i.appendChild(n),
n.styleSheet && !n.sheet
? (n.styleSheet.cssText = t)
: n.appendChild(document.createTextNode(t));
}),
(e.cssjs = t);
})(this);
!(function (u, D) {
"object" == typeof exports && "undefined" != typeof module
? (module.exports = D())
: "function" == typeof define && define.amd
? define(D)
: (u.JSON5 = D());
})(this, function () {
"use strict";
function u(u, D) {
return u((D = { exports: {} }), D.exports), D.exports;
}
var D = u(function (u) {
var D = (u.exports =
"undefined" != typeof window && window.Math == Math
? window
: "undefined" != typeof self && self.Math == Math
? self
: Function("return this")());
"number" == typeof __g && (__g = D);
}),
e = u(function (u) {
var D = (u.exports = { version: "2.6.5" });
"number" == typeof __e && (__e = D);
}),
r =
(e.version,
function (u) {
return "object" == typeof u ? null !== u : "function" == typeof u;
}),
t = function (u) {
if (!r(u)) throw TypeError(u + " is not an object!");
return u;
},
n = function (u) {
try {
return !!u();
} catch (u) {
return !0;
}
},
F = !n(function () {
return (
7 !=
Object.defineProperty({}, "a", {
get: function () {
return 7;
},
}).a
);
}),
C = D.document,
A = r(C) && r(C.createElement),
i =
!F &&
!n(function () {
return (
7 !=
Object.defineProperty(
((u = "div"), A ? C.createElement(u) : {}),
"a",
{
get: function () {
return 7;
},
}
).a
);
var u;
}),
E = Object.defineProperty,
o = {
f: F
? Object.defineProperty
: function (u, D, e) {
if (
(t(u),
(D = (function (u, D) {
if (!r(u)) return u;
var e, t;
if (
D &&
"function" == typeof (e = u.toString) &&
!r((t = e.call(u)))
)
return t;
if ("function" == typeof (e = u.valueOf) && !r((t = e.call(u))))
return t;
if (
!D &&
"function" == typeof (e = u.toString) &&
!r((t = e.call(u)))
)
return t;
throw TypeError("Can't convert object to primitive value");
})(D, !0)),
t(e),
i)
)
try {
return E(u, D, e);
} catch (u) {}
if ("get" in e || "set" in e)
throw TypeError("Accessors not supported!");
return "value" in e && (u[D] = e.value), u;
},
},
a = F
? function (u, D, e) {
return o.f(
u,
D,
(function (u, D) {
return {
enumerable: !(1 & u),
configurable: !(2 & u),
writable: !(4 & u),
value: D,
};
})(1, e)
);
}
: function (u, D, e) {
return (u[D] = e), u;
},
c = {}.hasOwnProperty,
B = function (u, D) {
return c.call(u, D);
},
s = 0,
f = Math.random(),
l = u(function (u) {
var r = D["__core-js_shared__"] || (D["__core-js_shared__"] = {});
(u.exports = function (u, D) {
return r[u] || (r[u] = void 0 !== D ? D : {});
})("versions", []).push({
version: e.version,
mode: "global",
copyright: "© 2019 Denis Pushkarev (zloirock.ru)",
});
})("native-function-to-string", Function.toString),
d = u(function (u) {
var r,
t = "Symbol(".concat(
void 0 === (r = "src") ? "" : r,
")_",
(++s + f).toString(36)
),
n = ("" + l).split("toString");
(e.inspectSource = function (u) {
return l.call(u);
}),
(u.exports = function (u, e, r, F) {
var C = "function" == typeof r;
C && (B(r, "name") || a(r, "name", e)),
u[e] !== r &&
(C && (B(r, t) || a(r, t, u[e] ? "" + u[e] : n.join(String(e)))),
u === D
? (u[e] = r)
: F
? u[e]
? (u[e] = r)
: a(u, e, r)
: (delete u[e], a(u, e, r)));
})(Function.prototype, "toString", function () {
return ("function" == typeof this && this[t]) || l.call(this);
});
}),
v = function (u, D, e) {
if (
((function (u) {
if ("function" != typeof u)
throw TypeError(u + " is not a function!");
})(u),
void 0 === D)
)
return u;
switch (e) {
case 1:
return function (e) {
return u.call(D, e);
};
case 2:
return function (e, r) {
return u.call(D, e, r);
};
case 3:
return function (e, r, t) {
return u.call(D, e, r, t);
};
}
return function () {
return u.apply(D, arguments);
};
},
p = function (u, r, t) {
var n,
F,
C,
A,
i = u & p.F,
E = u & p.G,
o = u & p.S,
c = u & p.P,
B = u & p.B,
s = E ? D : o ? D[r] || (D[r] = {}) : (D[r] || {}).prototype,
f = E ? e : e[r] || (e[r] = {}),
l = f.prototype || (f.prototype = {});
for (n in (E && (t = r), t))
(C = ((F = !i && s && void 0 !== s[n]) ? s : t)[n]),
(A =
B && F
? v(C, D)
: c && "function" == typeof C
? v(Function.call, C)
: C),
s && d(s, n, C, u & p.U),
f[n] != C && a(f, n, A),
c && l[n] != C && (l[n] = C);
};
(D.core = e),
(p.F = 1),
(p.G = 2),
(p.S = 4),
(p.P = 8),
(p.B = 16),
(p.W = 32),
(p.U = 64),
(p.R = 128);
var h,
m = p,
g = Math.ceil,
y = Math.floor,
w = function (u) {
return isNaN((u = +u)) ? 0 : (u > 0 ? y : g)(u);
},
b =
((h = !1),
function (u, D) {
var e,
r,
t = String(
(function (u) {
if (null == u) throw TypeError("Can't call method on " + u);
return u;
})(u)
),
n = w(D),
F = t.length;
return n < 0 || n >= F
? h
? ""
: void 0
: (e = t.charCodeAt(n)) < 55296 ||
e > 56319 ||
n + 1 === F ||
(r = t.charCodeAt(n + 1)) < 56320 ||
r > 57343
? h
? t.charAt(n)
: e
: h
? t.slice(n, n + 2)
: r - 56320 + ((e - 55296) << 10) + 65536;
});
m(m.P, "String", {
codePointAt: function (u) {
return b(this, u);
},
});
e.String.codePointAt;
var S = Math.max,
x = Math.min,
N = String.fromCharCode,
P = String.fromCodePoint;
m(m.S + m.F * (!!P && 1 != P.length), "String", {
fromCodePoint: function (u) {
for (
var D, e, r, t = arguments, n = [], F = arguments.length, C = 0;
F > C;
) {
if (
((D = +t[C++]),
(r = 1114111),
((e = w((e = D))) < 0 ? S(e + r, 0) : x(e, r)) !== D)
)
throw RangeError(D + " is not a valid code point");
n.push(
D < 65536 ? N(D) : N(55296 + ((D -= 65536) >> 10), (D % 1024) + 56320)
);
}
return n.join("");
},
});
e.String.fromCodePoint;
var _,
O,
j,
I,
V,
J,
M,
k,
L,
T,
z,
H,
$,
R,
G = {
Space_Separator: /[\u1680\u2000-\u200A\u202F\u205F\u3000]/,
ID_Start:
/[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEA\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE83\uDE86-\uDE89\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F\uDFE0\uDFE1]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00-\uDD1E\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]/,
ID_Continue:
/[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u08D4-\u08E1\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u09FC\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9-\u0AFF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D00-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1CD0-\u1CD2\u1CD4-\u1CF9\u1D00-\u1DF9\u1DFB-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEA\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE3E\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC00-\uDC4A\uDC50-\uDC59\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDE00-\uDE3E\uDE47\uDE50-\uDE83\uDE86-\uDE99\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD47\uDD50-\uDD59]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F\uDFE0\uDFE1]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00-\uDD1E\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6\uDD00-\uDD4A\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF]/,
},
U = {
isSpaceSeparator: function (u) {
return "string" == typeof u && G.Space_Separator.test(u);
},
isIdStartChar: function (u) {
return (
"string" == typeof u &&
((u >= "a" && u <= "z") ||
(u >= "A" && u <= "Z") ||
"$" === u ||
"_" === u ||
G.ID_Start.test(u))
);
},
isIdContinueChar: function (u) {
return (
"string" == typeof u &&
((u >= "a" && u <= "z") ||
(u >= "A" && u <= "Z") ||
(u >= "0" && u <= "9") ||
"$" === u ||
"_" === u ||
"" === u ||
"" === u ||
G.ID_Continue.test(u))
);
},
isDigit: function (u) {
return "string" == typeof u && /[0-9]/.test(u);
},
isHexDigit: function (u) {
return "string" == typeof u && /[0-9A-Fa-f]/.test(u);
},
};
function Z() {
for (T = "default", z = "", H = !1, $ = 1; ; ) {
R = q();
var u = X[T]();
if (u) return u;
}
}
function q() {
if (_[I]) return String.fromCodePoint(_.codePointAt(I));
}
function W() {
var u = q();
return (
"\n" === u ? (V++, (J = 0)) : u ? (J += u.length) : J++,
u && (I += u.length),
u
);
}
var X = {
default: function () {
switch (R) {
case "\t":
case "\v":
case "\f":
case " ":
case " ":
case "\ufeff":
case "\n":
case "\r":
case "\u2028":
case "\u2029":
return void W();
case "/":
return W(), void (T = "comment");
case void 0:
return W(), K("eof");
}
if (!U.isSpaceSeparator(R)) return X[O]();
W();
},
comment: function () {
switch (R) {
case "*":
return W(), void (T = "multiLineComment");
case "/":
return W(), void (T = "singleLineComment");
}
throw ru(W());
},
multiLineComment: function () {
switch (R) {
case "*":
return W(), void (T = "multiLineCommentAsterisk");
case void 0:
throw ru(W());
}
W();
},
multiLineCommentAsterisk: function () {
switch (R) {
case "*":
return void W();
case "/":
return W(), void (T = "default");
case void 0:
throw ru(W());
}
W(), (T = "multiLineComment");
},
singleLineComment: function () {
switch (R) {
case "\n":
case "\r":
case "\u2028":
case "\u2029":
return W(), void (T = "default");
case void 0:
return W(), K("eof");
}
W();
},
value: function () {
switch (R) {
case "{":
case "[":
return K("punctuator", W());
case "n":
return W(), Q("ull"), K("null", null);
case "t":
return W(), Q("rue"), K("boolean", !0);
case "f":
return W(), Q("alse"), K("boolean", !1);
case "-":
case "+":
return "-" === W() && ($ = -1), void (T = "sign");
case ".":
return (z = W()), void (T = "decimalPointLeading");
case "0":
return (z = W()), void (T = "zero");
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
return (z = W()), void (T = "decimalInteger");
case "I":
return W(), Q("nfinity"), K("numeric", 1 / 0);
case "N":
return W(), Q("aN"), K("numeric", NaN);
case '"':
case "'":
return (H = '"' === W()), (z = ""), void (T = "string");
}
throw ru(W());
},
identifierNameStartEscape: function () {
if ("u" !== R) throw ru(W());
W();
var u = Y();
switch (u) {
case "$":
case "_":
break;
default:
if (!U.isIdStartChar(u)) throw nu();
}
(z += u), (T = "identifierName");
},
identifierName: function () {
switch (R) {
case "$":
case "_":
case "":
case "":
return void (z += W());
case "\\":
return W(), void (T = "identifierNameEscape");
}
if (!U.isIdContinueChar(R)) return K("identifier", z);
z += W();
},
identifierNameEscape: function () {
if ("u" !== R) throw ru(W());
W();
var u = Y();
switch (u) {
case "$":
case "_":
case "":
case "":
break;
default:
if (!U.isIdContinueChar(u)) throw nu();
}
(z += u), (T = "identifierName");
},
sign: function () {
switch (R) {
case ".":
return (z = W()), void (T = "decimalPointLeading");
case "0":
return (z = W()), void (T = "zero");
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
return (z = W()), void (T = "decimalInteger");
case "I":
return W(), Q("nfinity"), K("numeric", $ * (1 / 0));
case "N":
return W(), Q("aN"), K("numeric", NaN);
}
throw ru(W());
},
zero: function () {
switch (R) {
case ".":
return (z += W()), void (T = "decimalPoint");
case "e":
case "E":
return (z += W()), void (T = "decimalExponent");
case "x":
case "X":
return (z += W()), void (T = "hexadecimal");
}
return K("numeric", 0 * $);
},
decimalInteger: function () {
switch (R) {
case ".":
return (z += W()), void (T = "decimalPoint");
case "e":
case "E":
return (z += W()), void (T = "decimalExponent");
}
if (!U.isDigit(R)) return K("numeric", $ * Number(z));
z += W();
},
decimalPointLeading: function () {
if (U.isDigit(R)) return (z += W()), void (T = "decimalFraction");
throw ru(W());
},
decimalPoint: function () {
switch (R) {
case "e":
case "E":
return (z += W()), void (T = "decimalExponent");
}
return U.isDigit(R)
? ((z += W()), void (T = "decimalFraction"))
: K("numeric", $ * Number(z));
},
decimalFraction: function () {
switch (R) {
case "e":
case "E":
return (z += W()), void (T = "decimalExponent");
}
if (!U.isDigit(R)) return K("numeric", $ * Number(z));
z += W();
},
decimalExponent: function () {
switch (R) {
case "+":
case "-":
return (z += W()), void (T = "decimalExponentSign");
}
if (U.isDigit(R)) return (z += W()), void (T = "decimalExponentInteger");
throw ru(W());
},
decimalExponentSign: function () {
if (U.isDigit(R)) return (z += W()), void (T = "decimalExponentInteger");
throw ru(W());
},
decimalExponentInteger: function () {
if (!U.isDigit(R)) return K("numeric", $ * Number(z));
z += W();
},
hexadecimal: function () {
if (U.isHexDigit(R)) return (z += W()), void (T = "hexadecimalInteger");
throw ru(W());
},
hexadecimalInteger: function () {
if (!U.isHexDigit(R)) return K("numeric", $ * Number(z));
z += W();
},
string: function () {
switch (R) {
case "\\":
return (
W(),
void (z += (function () {
switch (q()) {
case "b":
return W(), "\b";
case "f":
return W(), "\f";
case "n":
return W(), "\n";
case "r":
return W(), "\r";
case "t":
return W(), "\t";
case "v":
return W(), "\v";
case "0":
if ((W(), U.isDigit(q()))) throw ru(W());
return "\0";
case "x":
return (
W(),
(function () {
var u = "",
D = q();
if (!U.isHexDigit(D)) throw ru(W());
if (((u += W()), (D = q()), !U.isHexDigit(D)))
throw ru(W());
return (u += W()), String.fromCodePoint(parseInt(u, 16));
})()
);
case "u":
return W(), Y();
case "\n":
case "\u2028":
case "\u2029":
return W(), "";
case "\r":
return W(), "\n" === q() && W(), "";
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
case void 0:
throw ru(W());
}
return W();
})())
);
case '"':
return H ? (W(), K("string", z)) : void (z += W());
case "'":
return H ? void (z += W()) : (W(), K("string", z));
case "\n":
case "\r":
throw ru(W());
case "\u2028":
case "\u2029":
!(function (u) {
console.warn(
"JSON5: '" +
Fu(u) +
"' in strings is not valid ECMAScript; consider escaping"
);
})(R);
break;
case void 0:
throw ru(W());
}
z += W();
},
start: function () {
switch (R) {
case "{":
case "[":
return K("punctuator", W());
}
T = "value";
},
beforePropertyName: function () {
switch (R) {
case "$":
case "_":
return (z = W()), void (T = "identifierName");
case "\\":
return W(), void (T = "identifierNameStartEscape");
case "}":
return K("punctuator", W());
case '"':
case "'":
return (H = '"' === W()), void (T = "string");
}
if (U.isIdStartChar(R)) return (z += W()), void (T = "identifierName");
throw ru(W());
},
afterPropertyName: function () {
if (":" === R) return K("punctuator", W());
throw ru(W());
},
beforePropertyValue: function () {
T = "value";
},
afterPropertyValue: function () {
switch (R) {
case ",":
case "}":
return K("punctuator", W());
}
throw ru(W());
},
beforeArrayValue: function () {
if ("]" === R) return K("punctuator", W());
T = "value";
},
afterArrayValue: function () {
switch (R) {
case ",":
case "]":
return K("punctuator", W());
}
throw ru(W());
},
end: function () {
throw ru(W());
},
};
function K(u, D) {
return { type: u, value: D, line: V, column: J };
}
function Q(u) {
for (var D = 0, e = u; D < e.length; D += 1) {
var r = e[D];
if (q() !== r) throw ru(W());
W();
}
}
function Y() {
for (var u = "", D = 4; D-- > 0; ) {
var e = q();
if (!U.isHexDigit(e)) throw ru(W());
u += W();
}
return String.fromCodePoint(parseInt(u, 16));
}
var uu = {
start: function () {
if ("eof" === M.type) throw tu();
Du();
},
beforePropertyName: function () {
switch (M.type) {
case "identifier":
case "string":
return (k = M.value), void (O = "afterPropertyName");
case "punctuator":
return void eu();
case "eof":
throw tu();
}
},
afterPropertyName: function () {
if ("eof" === M.type) throw tu();
O = "beforePropertyValue";
},
beforePropertyValue: function () {
if ("eof" === M.type) throw tu();
Du();
},
beforeArrayValue: function () {
if ("eof" === M.type) throw tu();
"punctuator" !== M.type || "]" !== M.value ? Du() : eu();
},
afterPropertyValue: function () {
if ("eof" === M.type) throw tu();
switch (M.value) {
case ",":
return void (O = "beforePropertyName");
case "}":
eu();
}
},
afterArrayValue: function () {
if ("eof" === M.type) throw tu();
switch (M.value) {
case ",":
return void (O = "beforeArrayValue");
case "]":
eu();
}
},
end: function () {},
};
function Du() {
var u;
switch (M.type) {
case "punctuator":
switch (M.value) {
case "{":
u = {};
break;
case "[":
u = [];
}
break;
case "null":
case "boolean":
case "numeric":
case "string":
u = M.value;
}
if (void 0 === L) L = u;
else {
var D = j[j.length - 1];
Array.isArray(D)
? D.push(u)
: Object.defineProperty(D, k, {
value: u,
writable: !0,
enumerable: !0,
configurable: !0,
});
}
if (null !== u && "object" == typeof u)
j.push(u),
(O = Array.isArray(u) ? "beforeArrayValue" : "beforePropertyName");
else {
var e = j[j.length - 1];
O =
null == e
? "end"
: Array.isArray(e)
? "afterArrayValue"
: "afterPropertyValue";
}
}
function eu() {
j.pop();
var u = j[j.length - 1];
O =
null == u
? "end"
: Array.isArray(u)
? "afterArrayValue"
: "afterPropertyValue";
}
function ru(u) {
return Cu(
void 0 === u
? "JSON5: invalid end of input at " + V + ":" + J
: "JSON5: invalid character '" + Fu(u) + "' at " + V + ":" + J
);
}
function tu() {
return Cu("JSON5: invalid end of input at " + V + ":" + J);
}
function nu() {
return Cu("JSON5: invalid identifier character at " + V + ":" + (J -= 5));
}
function Fu(u) {
var D = {
"'": "\\'",
'"': '\\"',
"\\": "\\\\",
"\b": "\\b",
"\f": "\\f",
"\n": "\\n",
"\r": "\\r",
"\t": "\\t",
"\v": "\\v",
"\0": "\\0",
"\u2028": "\\u2028",
"\u2029": "\\u2029",
};
if (D[u]) return D[u];
if (u < " ") {
var e = u.charCodeAt(0).toString(16);
return "\\x" + ("00" + e).substring(e.length);
}
return u;
}
function Cu(u) {
var D = new SyntaxError(u);
return (D.lineNumber = V), (D.columnNumber = J), D;
}
return {
parse: function (u, D) {
(_ = String(u)),
(O = "start"),
(j = []),
(I = 0),
(V = 1),
(J = 0),
(M = void 0),
(k = void 0),
(L = void 0);
do {
(M = Z()), uu[O]();
} while ("eof" !== M.type);
return "function" == typeof D
? (function u(D, e, r) {
var t = D[e];
if (null != t && "object" == typeof t)
if (Array.isArray(t))
for (var n = 0; n < t.length; n++) {
var F = String(n),
C = u(t, F, r);
void 0 === C
? delete t[F]
: Object.defineProperty(t, F, {
value: C,
writable: !0,
enumerable: !0,
configurable: !0,
});
}
else
for (var A in t) {
var i = u(t, A, r);
void 0 === i
? delete t[A]
: Object.defineProperty(t, A, {
value: i,
writable: !0,
enumerable: !0,
configurable: !0,
});
}
return r.call(D, e, t);
})({ "": L }, "", D)
: L;
},
stringify: function (u, D, e) {
var r,
t,
n,
F = [],
C = "",
A = "";
if (
(null == D ||
"object" != typeof D ||
Array.isArray(D) ||
((e = D.space), (n = D.quote), (D = D.replacer)),
"function" == typeof D)
)
t = D;
else if (Array.isArray(D)) {
r = [];
for (var i = 0, E = D; i < E.length; i += 1) {
var o = E[i],
a = void 0;
"string" == typeof o
? (a = o)
: ("number" == typeof o ||
o instanceof String ||
o instanceof Number) &&
(a = String(o)),
void 0 !== a && r.indexOf(a) < 0 && r.push(a);
}
}
return (
e instanceof Number
? (e = Number(e))
: e instanceof String && (e = String(e)),
"number" == typeof e
? e > 0 &&
((e = Math.min(10, Math.floor(e))), (A = " ".substr(0, e)))
: "string" == typeof e && (A = e.substr(0, 10)),
c("", { "": u })
);
function c(u, D) {
var e = D[u];
switch (
(null != e &&
("function" == typeof e.toJSON5
? (e = e.toJSON5(u))
: "function" == typeof e.toJSON && (e = e.toJSON(u))),
t && (e = t.call(D, u, e)),
e instanceof Number
? (e = Number(e))
: e instanceof String
? (e = String(e))
: e instanceof Boolean && (e = e.valueOf()),
e)
) {
case null:
return "null";
case !0:
return "true";
case !1:
return "false";
}
return "string" == typeof e
? B(e)
: "number" == typeof e
? String(e)
: "object" == typeof e
? Array.isArray(e)
? (function (u) {
if (F.indexOf(u) >= 0)
throw TypeError("Converting circular structure to JSON5");
F.push(u);
var D = C;
C += A;
for (var e, r = [], t = 0; t < u.length; t++) {
var n = c(String(t), u);
r.push(void 0 !== n ? n : "null");
}
if (0 === r.length) e = "[]";
else if ("" === A) {
var i = r.join(",");
e = "[" + i + "]";
} else {
var E = ",\n" + C,
o = r.join(E);
e = "[\n" + C + o + ",\n" + D + "]";
}
return F.pop(), (C = D), e;
})(e)
: (function (u) {
if (F.indexOf(u) >= 0)
throw TypeError("Converting circular structure to JSON5");
F.push(u);
var D = C;
C += A;
for (
var e, t, n = r || Object.keys(u), i = [], E = 0, o = n;
E < o.length;
E += 1
) {
var a = o[E],
B = c(a, u);
if (void 0 !== B) {
var f = s(a) + ":";
"" !== A && (f += " "), (f += B), i.push(f);
}
}
if (0 === i.length) e = "{}";
else if ("" === A) (t = i.join(",")), (e = "{" + t + "}");
else {
var l = ",\n" + C;
(t = i.join(l)), (e = "{\n" + C + t + ",\n" + D + "}");
}
return F.pop(), (C = D), e;
})(e)
: void 0;
}
function B(u) {
for (
var D = { "'": 0.1, '"': 0.2 },
e = {
"'": "\\'",
'"': '\\"',
"\\": "\\\\",
"\b": "\\b",
"\f": "\\f",
"\n": "\\n",
"\r": "\\r",
"\t": "\\t",
"\v": "\\v",
"\0": "\\0",
"\u2028": "\\u2028",
"\u2029": "\\u2029",
},
r = "",
t = 0;
t < u.length;
t++
) {
var F = u[t];
switch (F) {
case "'":
case '"':
D[F]++, (r += F);
continue;
case "\0":
if (U.isDigit(u[t + 1])) {
r += "\\x00";
continue;
}
}
if (e[F]) r += e[F];
else if (F < " ") {
var C = F.charCodeAt(0).toString(16);
r += "\\x" + ("00" + C).substring(C.length);
} else r += F;
}
var A =
n ||
Object.keys(D).reduce(function (u, e) {
return D[u] < D[e] ? u : e;
});
return A + (r = r.replace(new RegExp(A, "g"), e[A])) + A;
}
function s(u) {
if (0 === u.length) return B(u);
var D = String.fromCodePoint(u.codePointAt(0));
if (!U.isIdStartChar(D)) return B(u);
for (var e = D.length; e < u.length; e++)
if (!U.isIdContinueChar(String.fromCodePoint(u.codePointAt(e))))
return B(u);
return u;
}
},
};
});
'use strict';
if (typeof IS_DOCKER === 'undefined' || typeof IS_PROCESS_SERVER === 'undefined') {
var SESSION_OBJ = {};
var DOCS_OBJ = {};
}
var glb = {};
var func = {};
func.UI = {};
func.GLB = {};
func.mobile = {};
func.runtime = {};
func.runtime.bind = {};
func.runtime.program = {};
func.runtime.resources = {};
func.runtime.render = {};
func.runtime.session = {};
func.runtime.workers = {};
func.runtime.ui = {};
func.runtime.widgets = {};
glb.IS_STUDIO = null;
// Lodash replacement utilities
var xu_isEmpty = function (val) {
if (val == null) return true;
if (typeof val === 'boolean' || typeof val === 'number') return !val;
if (typeof val === 'string' || Array.isArray(val)) return val.length === 0;
if (val instanceof Map || val instanceof Set) return val.size === 0;
return Object.keys(val).length === 0;
};
var xu_isEqual = function (a, b) {
if (a === b) return true;
if (a == null || b == null) return a === b;
if (typeof a !== typeof b) return false;
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
if (typeof a !== 'object') return false;
var keysA = Object.keys(a);
var keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (var i = 0; i < keysA.length; i++) {
if (!Object.prototype.hasOwnProperty.call(b, keysA[i]) || !xu_isEqual(a[keysA[i]], b[keysA[i]])) return false;
}
return true;
};
var xu_get = function (obj, path, defaultVal) {
var keys = typeof path === 'string' ? path.split('.') : path;
var result = obj;
for (var i = 0; i < keys.length; i++) {
if (result == null) return defaultVal;
result = result[keys[i]];
}
return result === undefined ? defaultVal : result;
};
var xu_set = function (obj, path, value) {
var keys = typeof path === 'string' ? path.split('.') : path;
var current = obj;
for (var i = 0; i < keys.length - 1; i++) {
if (current[keys[i]] == null) current[keys[i]] = {};
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
return obj;
};
var xu_clone = function (value) {
if (Array.isArray(value)) return value.slice();
if (value && typeof value === 'object') return { ...value };
return value;
};
var xu_cloneDeep = function (value) {
if (typeof structuredClone === 'function') {
try {
return structuredClone(value);
} catch (error) {}
}
if (Array.isArray(value)) {
return value.map(function (item) {
return xu_cloneDeep(item);
});
}
if (value && typeof value === 'object') {
var ret = {};
Object.keys(value).forEach(function (key) {
ret[key] = xu_cloneDeep(value[key]);
});
return ret;
}
return value;
};
var xu_map = function (collection, iteratee) {
if (!collection) return [];
if (Array.isArray(collection)) {
return collection.map(function (value, index) {
return iteratee ? iteratee(value, index) : value;
});
}
return Object.keys(collection).map(function (key) {
return iteratee ? iteratee(collection[key], key) : collection[key];
});
};
var xu_forEach = function (collection, iteratee) {
if (!collection || typeof iteratee !== 'function') return collection;
if (Array.isArray(collection)) {
collection.forEach(function (value, index) {
iteratee(value, index);
});
return collection;
}
Object.keys(collection).forEach(function (key) {
iteratee(collection[key], key);
});
return collection;
};
var xu_find = function (collection, predicate) {
if (!collection || typeof predicate !== 'function') return undefined;
var values = Array.isArray(collection)
? collection
: Object.keys(collection).map(function (key) {
return collection[key];
});
for (var i = 0; i < values.length; i++) {
if (predicate(values[i], i)) return values[i];
}
};
var xu_findIndex = function (collection, predicate) {
if (!Array.isArray(collection) || typeof predicate !== 'function') return -1;
for (var i = 0; i < collection.length; i++) {
if (predicate(collection[i], i)) return i;
}
return -1;
};
var xu_reduce = function (collection, iteratee, accumulator) {
if (!collection || typeof iteratee !== 'function') return accumulator;
var keys = Array.isArray(collection) ? collection.map(function (_value, index) { return index; }) : Object.keys(collection);
var has_accumulator = arguments.length > 2;
var result = accumulator;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = Array.isArray(collection) ? collection[key] : collection[key];
if (!has_accumulator) {
result = value;
has_accumulator = true;
continue;
}
result = iteratee(result, value, key);
}
return result;
};
var xu_debounce = function (callback, wait) {
var timeout_id = null;
return function () {
var args = arguments;
var context = this;
clearTimeout(timeout_id);
timeout_id = setTimeout(function () {
callback.apply(context, args);
}, wait || 0);
};
};
var xu_toStringSafe = function (value) {
if (typeof value === 'string') return value;
if (value == null) return '';
return String(value);
};
var xu_some = function (collection, predicate) {
if (!collection || typeof predicate !== 'function') return false;
var values = Array.isArray(collection)
? collection
: Object.keys(collection).map(function (key) {
return collection[key];
});
for (var i = 0; i < values.length; i++) {
if (predicate(values[i], i)) return true;
}
return false;
};
var xu_has = function (obj, key) {
return !!obj && Object.prototype.hasOwnProperty.call(obj, key);
};
var xu_runtime_global = typeof globalThis !== 'undefined' ? globalThis : {};
if (typeof xu_runtime_global._ === 'undefined') {
xu_runtime_global._ = {
clone: xu_clone,
cloneDeep: xu_cloneDeep,
debounce: xu_debounce,
each: xu_forEach,
find: xu_find,
findIndex: xu_findIndex,
forEach: xu_forEach,
get: xu_get,
has: xu_has,
isArray: Array.isArray,
isEmpty: xu_isEmpty,
map: xu_map,
reduce: xu_reduce,
some: xu_some,
toString: xu_toStringSafe,
// lodash type-checks + common helpers (lodash was removed from the runtime;
// programs still call these on the global `_`).
isBoolean: function (v) { return typeof v === 'boolean'; },
isString: function (v) { return typeof v === 'string'; },
isNumber: function (v) { return typeof v === 'number'; },
isFunction: function (v) { return typeof v === 'function'; },
isObject: function (v) { return v !== null && (typeof v === 'object' || typeof v === 'function'); },
isPlainObject: function (v) { return v !== null && typeof v === 'object' && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null); },
isNil: function (v) { return v == null; },
isUndefined: function (v) { return v === undefined; },
isNull: function (v) { return v === null; },
isInteger: Number.isInteger,
isNaN: function (v) { return typeof v === 'number' && v !== v; },
keys: function (o) { return o ? Object.keys(o) : []; },
values: function (o) { return o ? Object.values(o) : []; },
size: function (o) { return o == null ? 0 : (typeof o.length === 'number' ? o.length : Object.keys(o).length); },
includes: function (c, v) { return c == null ? false : (typeof c.includes === 'function' ? c.includes(v) : Object.values(c).indexOf(v) > -1); },
filter: function (c, fn) { return (c ? (Array.isArray(c) ? c : Object.values(c)) : []).filter(function (x, i) { return fn(x, i); }); },
last: function (a) { return a && a.length ? a[a.length - 1] : undefined; },
first: function (a) { return a && a.length ? a[0] : undefined; },
head: function (a) { return a && a.length ? a[0] : undefined; },
uniq: function (a) { return a ? Array.from(new Set(a)) : []; },
compact: function (a) { return a ? a.filter(Boolean) : []; },
assign: Object.assign,
merge: function (t) { for (var i = 1; i < arguments.length; i++) Object.assign(t || {}, arguments[i]); return t; },
capitalize: function (s) { s = String(s == null ? '' : s); return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); },
noop: function () {},
};
}
if (typeof _ === 'undefined') {
var _ = xu_runtime_global._;
}
var PROJECT_OBJ = {};
var APP_OBJ = {};
var SESSION_ID = null;
var EXP_BUSY = false;
glb.PROTECTED_VARS = ['_NULL', '_THIS', '_FOR_KEY', '_FOR_VAL', '_ROWNO', '_ROWID', '_ROWDOC', '_KEY', '_VAL'];
// glb.newRecord = 999999;
func.common = {};
func.runtime.platform = {
get_global: function (name) {
try {
if (typeof globalThis === 'undefined') {
return null;
}
return globalThis?.[name] || null;
} catch (error) {
return null;
}
},
has_window: function () {
return !!func.runtime.platform.get_window();
},
has_document: function () {
return !!func.runtime.platform.get_document();
},
get_window: function () {
return func.runtime.platform.get_global('window');
},
get_document: function () {
return func.runtime.platform.get_global('document');
},
get_location: function () {
const win = func.runtime.platform.get_window();
return win?.location || null;
},
get_navigator: function () {
const win = func.runtime.platform.get_window();
if (win?.navigator) {
return win.navigator;
}
return func.runtime.platform.get_global('navi' + 'gator');
},
is_html_element: function (value) {
const html_element = func.runtime.platform.get_global('HTML' + 'Element');
if (typeof html_element !== 'function') {
return false;
}
return value instanceof html_element;
},
get_storage: function (type) {
const win = func.runtime.platform.get_window();
const storage_key = type === 'session' ? 'session' + 'Storage' : 'local' + 'Storage';
try {
if (!win) {
return null;
}
return win?.[storage_key] || null;
} catch (error) {
return null;
}
},
get_storage_item: function (key, type) {
const storage = func.runtime.platform.get_storage(type);
if (!storage) {
return null;
}
try {
return storage.getItem(key);
} catch (error) {
return null;
}
},
set_storage_item: function (key, value, type) {
const storage = func.runtime.platform.get_storage(type);
if (!storage) {
return false;
}
try {
storage.setItem(key, value);
return true;
} catch (error) {
return false;
}
},
get_cookie_item: function (key) {
if (!key) {
return null;
}
const doc = func.runtime.platform.get_document();
const cookie_string = doc?.cookie;
if (!cookie_string) {
return null;
}
const cookie_entry = cookie_string.split('; ').find(function (cookie) {
return cookie.startsWith(key + '=');
});
if (!cookie_entry) {
return null;
}
return cookie_entry.split('=').slice(1).join('=') || null;
},
get_url_href: function () {
return func.runtime.platform.get_location()?.href || '';
},
get_url_search: function () {
return func.runtime.platform.get_location()?.search || '';
},
get_url_hash: function () {
return func.runtime.platform.get_location()?.hash || '';
},
get_host: function () {
return func.runtime.platform.get_location()?.host || '';
},
get_hostname: function () {
return func.runtime.platform.get_location()?.hostname || '';
},
get_device_uuid: function () {
const win = func.runtime.platform.get_window();
return win?.device?.uuid || null;
},
get_device_name: function () {
const win = func.runtime.platform.get_window();
return win?.device?.name || null;
},
get_inner_size: function () {
const win = func.runtime.platform.get_window();
return {
width: win?.innerWidth || 0,
height: win?.innerHeight || 0,
};
},
add_window_listener: function (name, handler) {
const win = func.runtime.platform.get_window();
if (!win?.addEventListener) {
return false;
}
win.addEventListener(name, handler);
return true;
},
dispatch_body_event: function (event) {
const doc = func.runtime.platform.get_document();
if (!doc?.body?.dispatchEvent) {
return false;
}
doc.body.dispatchEvent(event);
return true;
},
reload_top_window: function () {
const win = func.runtime.platform.get_window();
if (!win?.top?.location?.reload) {
return false;
}
win.top.location.reload();
return true;
},
get_service_worker: function () {
const nav = func.runtime.platform.get_navigator();
return nav?.serviceWorker || null;
},
has_service_worker: function () {
return !!func.runtime.platform.get_service_worker();
},
register_service_worker: function (script_url) {
const service_worker = func.runtime.platform.get_service_worker();
if (!service_worker?.register) {
return Promise.reject(new Error('serviceWorker is not available'));
}
return service_worker.register(script_url);
},
add_service_worker_listener: function (name, handler) {
const service_worker = func.runtime.platform.get_service_worker();
if (!service_worker?.addEventListener) {
return false;
}
service_worker.addEventListener(name, handler);
return true;
},
};
// ── Platform-agnostic event bus ──
// Works in browser, worker, and Node environments.
// In browser, bridge DOM events into this bus so core code never touches $(document) directly.
func.runtime.platform._event_bus = {};
func.runtime.platform.on = function (name, handler) {
if (!func.runtime.platform._event_bus[name]) {
func.runtime.platform._event_bus[name] = [];
}
func.runtime.platform._event_bus[name].push(handler);
};
func.runtime.platform.off = function (name, handler) {
const handlers = func.runtime.platform._event_bus[name];
if (!handlers) return;
if (!handler) {
delete func.runtime.platform._event_bus[name];
return;
}
const index = handlers.indexOf(handler);
if (index !== -1) {
handlers.splice(index, 1);
}
};
func.runtime.platform._emitting = {};
func.runtime.platform.emit = function (name, data) {
// re-entrancy guard: prevent infinite loops when DOM bridge triggers the same event
if (func.runtime.platform._emitting[name]) return;
func.runtime.platform._emitting[name] = true;
try {
const handlers = func.runtime.platform._event_bus[name];
if (handlers) {
for (let i = 0; i < handlers.length; i++) {
handlers[i](data);
}
}
if (typeof func.runtime.platform.dispatch_document_event === 'function') {
func.runtime.platform.dispatch_document_event(name, data);
}
} finally {
func.runtime.platform._emitting[name] = false;
}
};
// ── Platform helpers for DOM-independent resource loading ──
func.runtime.platform.apply_element_attributes = function (node, attributes, excluded_keys = []) {
if (!node?.setAttribute || !attributes) {
return node;
}
const excluded = new Set(excluded_keys || []);
const attr_keys = Object.keys(attributes);
for (let index = 0; index < attr_keys.length; index++) {
const key = attr_keys[index];
if (!key || excluded.has(key)) {
continue;
}
const value = attributes[key];
if (value === false || typeof value === 'undefined') {
continue;
}
node.setAttribute(key, value === null ? '' : `${value}`);
}
return node;
};
func.runtime.platform.load_script = function (url, type, callback, attributes) {
const normalized_url = typeof url === 'string' ? url.trim() : '';
if (!normalized_url || normalized_url === 'undefined' || normalized_url === 'null') {
if (callback) {
callback();
}
return null;
}
const doc = func.runtime.platform.get_document();
if (!doc?.createElement || !doc?.head?.appendChild) {
if (callback) {
callback();
}
return;
}
const find_existing_script = function () {
const asset_key = attributes?.['data-xuda-asset-key'];
const scripts = doc.querySelectorAll ? Array.from(doc.querySelectorAll('script')) : [];
return scripts.find(function (script) {
if (asset_key && script.getAttribute('data-xuda-asset-key') === asset_key) {
return true;
}
return script.getAttribute('src') === normalized_url;
}) || null;
};
const existing_script = find_existing_script();
if (existing_script) {
if (callback) {
if (existing_script.getAttribute('data-xuda-loaded') === 'true' || !url) {
callback();
} else {
existing_script.addEventListener('load', callback, { once: true });
existing_script.addEventListener('error', callback, { once: true });
}
}
return existing_script;
}
const script = doc.createElement('script');
script.src = normalized_url;
if (type) script.type = type;
func.runtime.platform.apply_element_attributes(script, attributes, ['src', 'type']);
script.onload = function () {
script.setAttribute('data-xuda-loaded', 'true');
if (callback) {
callback();
}
};
script.onerror = function () {
if (callback) {
callback();
}
};
doc.head.appendChild(script);
return script;
};
func.runtime.platform.load_css = function (href, attributes) {
const normalized_href = typeof href === 'string' ? href.trim() : '';
if (!normalized_href || normalized_href === 'undefined' || normalized_href === 'null') {
return null;
}
const doc = func.runtime.platform.get_document();
if (!doc?.createElement || !doc?.head) {
return;
}
try {
const asset_key = attributes?.['data-xuda-asset-key'];
const existing_links = doc.querySelectorAll ? Array.from(doc.querySelectorAll('link')) : [];
const existing = existing_links.find(function (link) {
if (asset_key && link.getAttribute('data-xuda-asset-key') === asset_key) {
return true;
}
return link.getAttribute('href') === normalized_href;
});
if (existing) return existing;
} catch (err) {
return;
}
const link = doc.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = normalized_href;
func.runtime.platform.apply_element_attributes(link, attributes, ['href']);
doc.head.insertBefore(link, doc.head.firstChild);
return link;
};
func.runtime.platform.remove_js_css = function (filename, filetype) {
const doc = func.runtime.platform.get_document();
if (!doc?.getElementsByTagName) return;
const tagName = filetype === 'js' ? 'script' : filetype === 'css' ? 'link' : 'none';
const attr = filetype === 'js' ? 'src' : filetype === 'css' ? 'href' : 'none';
const elements = doc.getElementsByTagName(tagName);
for (let i = elements.length - 1; i >= 0; i--) {
if (elements[i] && elements[i].getAttribute(attr) != null && elements[i].getAttribute(attr).indexOf(filename) !== -1) {
elements[i].parentNode.removeChild(elements[i]);
}
}
};
func.runtime.platform.inject_css = function (cssText) {
const doc = func.runtime.platform.get_document();
if (!doc?.createElement || !doc?.head?.appendChild || !cssText) return;
const style = doc.createElement('style');
style.type = 'text/css';
style.textContent = cssText;
doc.head.appendChild(style);
};
func.runtime.platform.set_title = function (title) {
const doc = func.runtime.platform.get_document();
if (doc) {
doc.title = title;
}
};
func.runtime.platform.set_cursor = function (element, cursor) {
const node = func.runtime.ui?.get_first_node ? func.runtime.ui.get_first_node(element) : element;
if (node?.style) {
node.style.cursor = cursor;
}
};
func.runtime.program.normalize_doc_for_runtime = function (doc) {
if (!doc || doc.__xudaRuntimeNormalized || !Array.isArray(doc.progUi) || !doc.progUi.length) {
return doc;
}
const normalize_tag_name = function (tag_name) {
return `${tag_name || ''}`.trim().toLowerCase();
};
const merge_attributes = function (target, source) {
const merged = { ...(target || {}) };
const source_attributes = source || {};
const keys = Object.keys(source_attributes);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
const value = source_attributes[key];
if (typeof value === 'undefined') {
continue;
}
if (key === 'class' && merged.class && value) {
const next_value = `${merged.class} ${value}`.trim();
merged.class = Array.from(new Set(next_value.split(/\s+/).filter(Boolean))).join(' ');
continue;
}
if (key === 'style' && merged.style && value) {
merged.style = `${merged.style}; ${value}`.trim();
continue;
}
if (typeof merged[key] === 'undefined') {
merged[key] = value;
}
}
return merged;
};
const get_attribute_source = function (source) {
if (!source) {
return {};
}
if (typeof source === 'string') {
try {
const parsed = JSON.parse(source);
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
} catch (_) {
return {};
}
}
return typeof source === 'object' && !Array.isArray(source) ? source : {};
};
const normalize_node_attributes = function (node) {
let attributes = get_attribute_source(node?.attributes);
attributes = merge_attributes(attributes, get_attribute_source(node?.attributes_raw_obj));
attributes = merge_attributes(attributes, get_attribute_source(node?.attributes_raw));
return attributes;
};
const normalize_nodes = function (nodes, state) {
const normalized_nodes = [];
for (let index = 0; index < (nodes || []).length; index++) {
const node = nodes[index];
if (!node || typeof node !== 'object') {
continue;
}
const tag_name = normalize_tag_name(node.tagName);
if (tag_name === '!doctype') {
state.changed = true;
continue;
}
if (tag_name === 'html') {
state.changed = true;
state.root_attributes = merge_attributes(state.root_attributes, normalize_node_attributes(node));
normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
continue;
}
if (tag_name === 'head') {
state.changed = true;
normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
continue;
}
if (tag_name === 'body') {
state.changed = true;
state.root_attributes = merge_attributes(state.root_attributes, normalize_node_attributes(node));
normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
continue;
}
let next_node = node;
const merged_node_attributes = normalize_node_attributes(node);
if (!xu_isEqual(merged_node_attributes, node.attributes || {})) {
next_node = {
...next_node,
attributes: merged_node_attributes,
};
state.changed = true;
}
if (Array.isArray(node.children) && node.children.length) {
const next_children = normalize_nodes(node.children, state);
if (next_children !== node.children) {
next_node = {
...next_node,
children: next_children,
};
state.changed = true;
}
}
normalized_nodes.push(next_node);
}
return normalized_nodes;
};
const [root_node, ...extra_nodes] = doc.progUi;
if (!root_node || typeof root_node !== 'object') {
return doc;
}
const state = {
changed: false,
root_attributes: {},
};
const root_node_attributes = normalize_node_attributes(root_node);
if (!xu_isEqual(root_node_attributes, root_node.attributes || {})) {
state.changed = true;
}
const normalized_children = normalize_nodes([...(root_node.children || []), ...extra_nodes], state);
const merged_attributes = merge_attributes(root_node_attributes, state.root_attributes);
if (!state.changed && !Object.keys(state.root_attributes).length) {
doc.__xudaRuntimeNormalized = true;
return doc;
}
return {
...doc,
__xudaRuntimeNormalized: true,
progUi: [
{
...root_node,
attributes: merged_attributes,
children: normalized_children,
},
],
};
};
func.runtime.env = {
get_url_params: function () {
const search = func.runtime.platform.get_url_search();
return new URLSearchParams(search);
},
get_url_parameters_object: function () {
const search_params = func.runtime.env.get_url_params();
const parameters = {};
for (const [key, value] of search_params.entries()) {
parameters[key] = value;
}
return parameters;
},
get_default_session_value: function (key) {
switch (key) {
case 'domain':
return func.runtime.platform.get_host();
case 'engine_mode':
return 'miniapp';
case 'app_id':
return 'unknown';
default:
return null;
}
},
};
func.runtime.session.create_tab_id = function () {
const session_storage = func.runtime.platform.get_storage('session');
const local_storage = func.runtime.platform.get_storage('local');
var page_tab_id = session_storage?.getItem('tabID');
if (page_tab_id == null) {
var local_tab_id = local_storage?.getItem('tabID');
page_tab_id = local_tab_id == null ? 1 : Number(local_tab_id) + 1;
func.runtime.platform.set_storage_item('tabID', page_tab_id, 'local');
func.runtime.platform.set_storage_item('tabID', page_tab_id, 'session');
}
return page_tab_id;
};
func.runtime.session.get_fingerprint = function (components, instance_id) {
const device_uuid = func.runtime.platform.get_device_uuid();
if (func.utils.get_device() && device_uuid) {
if (instance_id) {
return instance_id + device_uuid;
}
return device_uuid;
}
const fingerprint_id = Fingerprint2.x64hash128(
components
.map(function (pair) {
return pair.value;
})
.join(),
31,
);
if (instance_id) {
return instance_id + fingerprint_id + func.runtime.session.create_tab_id();
}
return fingerprint_id;
};
func.runtime.session.create_state = function (SESSION_ID, options) {
const runtime_host = func.runtime.platform.get_host();
SESSION_OBJ[SESSION_ID] = {
JOB_NO: 1000,
opt: options.opt,
root_element: options.root_element,
worker_type: options.worker_type,
api_callback: options.api_callback,
CODE_BUNDLE: options.code_bundle,
SLIM_BUNDLE: options.slim_bundle,
WORKER_OBJ: {
jobs: [],
num: 1000,
stat: null,
},
DS_GLB: {},
SYS_GLOBAL_OBJ_FIREBASE_AUTH_INFO: {
token: '',
first_name: '',
last_name: '',
email: '',
user_id: '',
picture: '',
verified_email: '',
locale: '',
error_code: '',
error_msg: '',
},
SYS_GLOBAL_OBJ_CLIENT_INFO: {
fingerprint: '',
device: '',
user_agent: '',
browser_version: '',
browser_name: '',
engine_version: '',
client_ip: '',
engine_name: '',
os_name: '',
os_version: '',
device_model: '',
device_vendor: '',
device_type: '',
screen_current_resolution_x: '',
screen_current_resolution_y: '',
screen_available_resolution_x: '',
screen_available_resolution_y: '',
language: '',
time_zone: '',
cpu_architecture: '',
uuid: '',
},
PUSH_NOTIFICATION_GRANTED: null,
FIREBASE_TOKEN_ID: null,
USR_OBJ: {},
debug_js: null,
DS_UI_EVENTS_GLB: {},
host: runtime_host,
req_id: 0,
build_info: {},
CACHE_REQ: {},
url_params: {
...func.common.getParametersFromUrl(),
...options.url_params,
},
};
func.runtime.workers.ensure_registry(SESSION_ID);
return SESSION_OBJ[SESSION_ID];
};
func.runtime.session.is_slim = function (SESSION_ID) {
const session = typeof SESSION_ID === 'undefined' || SESSION_ID === null ? null : SESSION_OBJ?.[SESSION_ID];
if (session && typeof session.SLIM_BUNDLE !== 'undefined') {
return !!session.SLIM_BUNDLE;
}
return !!glb.SLIM_BUNDLE;
};
func.runtime.session.set_default_value = function (_session, key, value) {
_session[key] = value || func.runtime.env.get_default_session_value(key);
return _session[key];
};
func.runtime.session.populate_client_info = function (_session, components) {
const _client_info = _session.SYS_GLOBAL_OBJ_CLIENT_INFO;
const platform = func.runtime.platform;
const { engine_mode } = _session;
_client_info.fingerprint = func.runtime.session.get_fingerprint(components);
if (engine_mode === 'live_preview') {
const inner_size = platform.get_inner_size();
_client_info.screen_current_resolution_x = inner_size.width;
_client_info.screen_current_resolution_y = inner_size.height;
_client_info.screen_available_resolution_x = inner_size.width;
_client_info.screen_available_resolution_y = inner_size.height;
} else {
_client_info.screen_current_resolution_x = components[6].value[0];
_client_info.screen_current_resolution_y = components[6].value[1];
_client_info.screen_available_resolution_x = components[7].value[0];
_client_info.screen_available_resolution_y = components[7].value[1];
}
const client = new ClientJS();
_client_info.device = func.utils.get_device();
const browser_data = client.getBrowserData();
_client_info.user_agent = browser_data.ua;
_client_info.browser_version = browser_data.browser.name;
_client_info.browser_name = browser_data.browser.version;
_client_info.engine_version = browser_data.engine.name;
_client_info.engine_name = browser_data.engine.version;
_client_info.os_name = browser_data.os.name;
_client_info.os_version = browser_data.os.version;
_client_info.device_model = browser_data.device.name;
_client_info.device_vendor = browser_data.device.name;
_client_info.device_type = browser_data.device.name;
_client_info.language = client.getLanguage();
_client_info.time_zone = client.getTimeZone();
_client_info.cpu_architecture = browser_data.cpu.architecture;
if (['android', 'ios', 'windows', 'macos', 'linux', 'live_preview'].includes(engine_mode) && func.utils.get_device()) {
_client_info.uuid = platform.get_device_uuid();
const device_name = platform.get_device_name();
if (device_name) {
_client_info.device_name = device_name;
}
}
return _client_info;
};
func.runtime.workers.ensure_registry = function (SESSION_ID) {
if (!WEB_WORKER[SESSION_ID]) {
WEB_WORKER[SESSION_ID] = {};
}
return WEB_WORKER[SESSION_ID];
};
func.runtime.workers.get_registry_entry = function (SESSION_ID, worker_id) {
return func.runtime.workers.ensure_registry(SESSION_ID)?.[worker_id] || null;
};
func.runtime.workers.set_registry_entry = function (SESSION_ID, worker_id, entry) {
const worker_registry = func.runtime.workers.ensure_registry(SESSION_ID);
worker_registry[worker_id] = entry;
return worker_registry[worker_id];
};
func.runtime.workers.build_worker_name = function (glb_worker_type, session, prog_obj, worker_id, build_id) {
return (
`${typeof session.SLIM_BUNDLE === 'undefined' || !session.SLIM_BUNDLE ? '' : 'Slim '}${prog_obj.menuName} worker` +
' ' +
glb_worker_type +
': #' +
worker_id.toString() +
' ' +
(build_id || '') +
' ' +
session.domain
);
};
func.runtime.workers.is_server_transport = function (session) {
return !!(RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED && (!session.opt.app_computing_mode || session.opt.app_computing_mode === 'server'));
};
func.runtime.workers.send_message = function (SESSION_ID, worker_id, session, msg, process_pid) {
const registry_entry = func.runtime.workers.get_registry_entry(SESSION_ID, worker_id);
if (!registry_entry?.worker) {
return false;
}
if (func.runtime.workers.is_server_transport(session)) {
if (process_pid) {
msg.process_pid = process_pid;
}
registry_entry.worker.emit('message', msg);
return true;
}
registry_entry.worker.postMessage(msg);
return true;
};
func.runtime.workers.set_promise = function (SESSION_ID, worker_id, promise_queue_id, value) {
const registry_entry = func.runtime.workers.get_registry_entry(SESSION_ID, worker_id);
if (!registry_entry) {
return null;
}
registry_entry.promise_queue[promise_queue_id] = value;
return registry_entry.promise_queue[promise_queue_id];
};
func.runtime.workers.get_promise = function (SESSION_ID, worker_id, promise_queue_id) {
const registry_entry = func.runtime.workers.get_registry_entry(SESSION_ID, worker_id);
if (!registry_entry) {
return null;
}
return registry_entry.promise_queue[promise_queue_id];
};
func.runtime.workers.delete_promise = function (SESSION_ID, worker_id, promise_queue_id) {
const registry_entry = func.runtime.workers.get_registry_entry(SESSION_ID, worker_id);
if (!registry_entry?.promise_queue) {
return false;
}
delete registry_entry.promise_queue[promise_queue_id];
return true;
};
func.runtime.render.clone_runtime_options = function (value) {
if (typeof structuredClone === 'function') {
try {
return structuredClone(value);
} catch (_) {}
}
if (Array.isArray(value)) {
return value.map(function (item) {
return func.runtime.render.clone_runtime_options(item);
});
}
if (value && typeof value === 'object') {
const cloned = {};
const keys = Object.keys(value);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
cloned[key] = func.runtime.render.clone_runtime_options(value[key]);
}
return cloned;
}
return value;
};
func.runtime.render.normalize_runtime_bootstrap = function (raw_options = {}) {
const options = raw_options || {};
let app_computing_mode = options.app_computing_mode || '';
let app_render_mode = options.app_render_mode || '';
let app_client_activation = options.app_client_activation || '';
let ssr_payload = options.ssr_payload || null;
if (typeof ssr_payload === 'string') {
try {
ssr_payload = JSON.parse(ssr_payload);
} catch (_) {
ssr_payload = null;
}
}
if (ssr_payload && typeof ssr_payload === 'object') {
ssr_payload = func.runtime.render.clone_runtime_options(ssr_payload);
}
if (!app_computing_mode) {
if (app_render_mode === 'ssr_first_page' || app_render_mode === 'ssr_full') {
app_computing_mode = 'server';
} else {
app_computing_mode = 'main';
}
}
switch (app_computing_mode) {
case 'main':
app_render_mode = 'csr';
app_client_activation = 'none';
break;
case 'worker':
app_render_mode = 'csr';
app_client_activation = 'none';
break;
default:
app_computing_mode = 'server';
if (app_render_mode !== 'ssr_full') {
app_render_mode = 'ssr_first_page';
}
app_client_activation = app_render_mode === 'ssr_full' ? 'hydrate' : 'takeover';
break;
}
if (ssr_payload && typeof ssr_payload === 'object') {
if (!ssr_payload.app_render_mode) {
ssr_payload.app_render_mode = app_render_mode;
}
if (!ssr_payload.app_client_activation) {
ssr_payload.app_client_activation = app_client_activation;
}
if (!ssr_payload.app_computing_mode) {
ssr_payload.app_computing_mode = app_computing_mode;
}
}
return {
app_computing_mode,
app_render_mode,
app_client_activation,
ssr_payload,
};
};
func.runtime.render.apply_runtime_bootstrap_defaults = function (target = {}) {
const normalized = func.runtime.render.normalize_runtime_bootstrap(target);
target.app_computing_mode = normalized.app_computing_mode;
target.app_render_mode = normalized.app_render_mode;
target.app_client_activation = normalized.app_client_activation;
target.ssr_payload = normalized.ssr_payload;
return normalized;
};
func.runtime.render.is_server_render_mode = function (target = {}) {
const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
return normalized.app_computing_mode === 'server' && normalized.app_render_mode !== 'csr';
};
func.runtime.render.is_takeover_mode = function (target = {}) {
const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
return normalized.app_client_activation === 'takeover';
};
func.runtime.render.is_hydration_mode = function (target = {}) {
const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
return normalized.app_client_activation === 'hydrate';
};
func.runtime.render.get_ssr_payload = function (target = {}) {
if (target?.opt?.ssr_payload) {
return target.opt.ssr_payload;
}
if (target?.ssr_payload) {
return target.ssr_payload;
}
const win = func.runtime.platform.get_window();
return win?.__XUDA_SSR__ || null;
};
func.runtime.render.should_use_ssr_payload = function (SESSION_ID, paramsP) {
const session = SESSION_OBJ?.[SESSION_ID];
const payload = func.runtime.render.get_ssr_payload(session);
if (!payload || payload._consumed) {
return false;
}
if (paramsP?.prog_id && payload.prog_id && payload.prog_id !== paramsP.prog_id) {
return false;
}
return true;
};
func.runtime.render.mark_ssr_payload_consumed = function (SESSION_ID) {
const session = SESSION_OBJ?.[SESSION_ID];
const payload = func.runtime.render.get_ssr_payload(session);
if (!payload || typeof payload !== 'object') {
return false;
}
payload._consumed = true;
return true;
};
func.runtime.render.get_root_data_system = function (SESSION_ID) {
return SESSION_OBJ[SESSION_ID]?.DS_GLB?.[0]?.data_system || null;
};
func.runtime.render.resolve_xu_for_source = async function (SESSION_ID, dsSessionP, value) {
let arr = value;
let reference_source_obj;
const normalized_reference = typeof value === 'string' && value.startsWith('@') ? value.substring(1) : value;
const _progFields = await func.datasource.get_progFields(SESSION_ID, dsSessionP);
let view_field_obj = func.common.find_item_by_key(_progFields, 'field_id', normalized_reference);
if (view_field_obj || normalized_reference !== value) {
reference_source_obj = await func.datasource.get_value(SESSION_ID, normalized_reference, dsSessionP);
arr = reference_source_obj?.ret?.value;
} else {
if (typeof value === 'string') {
arr = eval(value.replaceAll('\\', ''));
}
if (typeof arr === 'number') {
arr = Array.from(Array(arr).keys());
}
}
return {
arr,
reference_source_obj,
};
};
func.runtime.render.apply_iterate_value_to_ds = function (SESSION_ID, dsSessionP, currentRecordId, progFields, field_id, value, is_dynamic_field) {
if (is_dynamic_field) {
func.datasource.add_dynamic_field_to_ds(SESSION_ID, dsSessionP, field_id, value);
return true;
}
let view_field_obj = func.common.find_item_by_key(progFields || [], 'field_id', field_id);
if (!view_field_obj) {
console.error('field not exist in dataset for xu-for method');
return false;
}
let _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
try {
const row_idx = func.common.find_ROWID_idx(_ds, currentRecordId);
_ds.data_feed.rows[row_idx][field_id] = value;
return true;
} catch (err) {
console.error(err);
return false;
}
};
func.runtime.render.build_iterate_info = function (options) {
return {
_val: options._val,
_key: options._key,
iterator_key: options.iterator_key,
iterator_val: options.iterator_val,
is_key_dynamic_field: options.is_key_dynamic_field,
is_val_dynamic_field: options.is_val_dynamic_field,
reference_source_obj: options.reference_source_obj,
};
};
func.runtime.render.apply_iterate_info_to_current_record = function (SESSION_ID, dsSessionP, currentRecordId, progFields, iterate_info) {
if (!iterate_info) {
return false;
}
func.runtime.render.apply_iterate_value_to_ds(SESSION_ID, dsSessionP, currentRecordId, progFields, iterate_info.iterator_key, iterate_info._key, iterate_info.is_key_dynamic_field);
func.runtime.render.apply_iterate_value_to_ds(SESSION_ID, dsSessionP, currentRecordId, progFields, iterate_info.iterator_val, iterate_info._val, iterate_info.is_val_dynamic_field);
return true;
};
func.runtime.render.sync_iterate_info_to_dataset = function (_ds, iterate_info) {
if (!iterate_info) {
return false;
}
const sync_field = function (field_id, value, is_dynamic_field) {
if (is_dynamic_field) {
_ds.dynamic_fields[field_id].value = value;
return true;
}
try {
const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
_ds.data_feed.rows[row_idx][field_id] = value;
return true;
} catch (err) {
console.error(err);
return false;
}
};
sync_field(iterate_info.iterator_key, iterate_info._key, iterate_info.is_key_dynamic_field);
sync_field(iterate_info.iterator_val, iterate_info._val, iterate_info.is_val_dynamic_field);
return true;
};
func.runtime.program.get_params_obj = async function (SESSION_ID, prog_id, nodeP, dsSession) {
const _prog = await func.utils.VIEWS_OBJ.get(SESSION_ID, prog_id);
if (!_prog) return;
let params_res = {},
params_raw = {};
if (_prog?.properties?.progParams) {
for await (const [key, val] of Object.entries(_prog.properties.progParams)) {
if (!['in', 'out'].includes(val.data.dir)) continue;
if (nodeP.attributes) {
if (Object.prototype.hasOwnProperty.call(nodeP.attributes, val.data.parameter)) {
params_res[val.data.parameter] = nodeP.attributes[val.data.parameter];
} else if (Object.prototype.hasOwnProperty.call(nodeP.attributes, `xu-exp:${val.data.parameter}`)) {
if (val.data.dir == 'out') {
params_res[val.data.parameter] = nodeP.attributes[`xu-exp:${val.data.parameter}`].replaceAll('@', '');
} else {
let ret = await func.expression.get(SESSION_ID, nodeP.attributes[`xu-exp:${val.data.parameter}`], dsSession, 'parameters');
params_res[val.data.parameter] = ret.result;
params_raw[val.data.parameter] = nodeP.attributes[`xu-exp:${val.data.parameter}`];
}
}
continue;
}
console.warn(`Warning: Program ${_prog.properties.menuName} expected In parameter: ${val.data.parameter} but received null instead`);
}
}
return { params_res, params_raw };
};
func.runtime.bind.build_datasource_changes = function (dsSessionP, currentRecordId, field_id, value) {
return {
[dsSessionP]: {
[currentRecordId]: {
[field_id]: value,
},
},
};
};
func.runtime.bind.get_bind_node = function (elm) {
if (!elm) {
return null;
}
if (elm.nodeType) {
return elm;
}
if (Array.isArray(elm) || typeof elm.length === 'number') {
return elm[0] || null;
}
return null;
};
func.runtime.bind.is_value_node = function (node) {
if (!node?.tagName) {
return false;
}
const tag_name = node.tagName.toLowerCase();
return ['input', 'select', 'textarea'].includes(tag_name) || typeof node.value !== 'undefined';
};
func.runtime.bind.get_bind_value_node = function (elm) {
const node = func.runtime.bind.get_bind_node(elm);
if (!node) {
return null;
}
if (func.runtime.bind.is_value_node(node)) {
return node;
}
return node.querySelector?.('input, select, textarea') || node;
};
func.runtime.bind.should_use_live_text_listener = function (elm) {
const node = func.runtime.bind.get_bind_value_node(elm);
if (!node?.tagName) {
return false;
}
const tag_name = node.tagName.toLowerCase();
const type = (node.type || node.getAttribute?.('type') || '').toLowerCase();
if (tag_name === 'textarea') {
return true;
}
if (tag_name !== 'input') {
return false;
}
return !['button', 'checkbox', 'file', 'hidden', 'image', 'radio', 'reset', 'submit'].includes(type);
};
func.runtime.bind.get_live_text_debounce_ms = function () {
return 200;
};
func.runtime.bind.to_finite_number = function (value) {
if (value === '' || value === null || typeof value === 'undefined') {
return null;
}
const numeric_value = Number(value);
return Number.isFinite(numeric_value) ? numeric_value : null;
};
func.runtime.bind.get_select_option_number = function (option) {
if (!option) {
return null;
}
const candidate_attributes = ['value', 'data-value', 'data-xuda-value', 'data-xu-value', 'xu-value'];
for (let index = 0; index < candidate_attributes.length; index++) {
const attr_value = option.getAttribute?.(candidate_attributes[index]);
const numeric_value = func.runtime.bind.to_finite_number(attr_value);
if (numeric_value !== null) {
return numeric_value;
}
}
return null;
};
func.runtime.bind.remember_select_numeric_context = function (elm, field_type, value) {
if (field_type !== 'number' || elm?.tagName?.toLowerCase?.() !== 'select') {
return false;
}
const numeric_value = func.runtime.bind.to_finite_number(value);
if (numeric_value === null || elm.selectedIndex < 0) {
return false;
}
elm.__xuda_select_numeric_bind_context = {
selectedIndex: elm.selectedIndex,
value: numeric_value,
};
return true;
};
func.runtime.bind.apply_select_value_once = function (elm, value, field_type) {
const options = Array.from(elm.options || []);
if (!options.length) {
return false;
}
const string_value = value === null || typeof value === 'undefined' ? '' : String(value);
const numeric_value = func.runtime.bind.to_finite_number(value);
let matched_index = -1;
if (field_type === 'number' && numeric_value !== null) {
matched_index = options.findIndex(function (option) {
return func.runtime.bind.get_select_option_number(option) === numeric_value;
});
if (matched_index < 0 && Number.isInteger(numeric_value)) {
if (options[numeric_value - 1]) {
matched_index = numeric_value - 1;
} else if (options[numeric_value]) {
matched_index = numeric_value;
}
}
} else {
matched_index = options.findIndex(function (option) {
return String(option.value) === string_value || String(option.getAttribute?.('value')) === string_value;
});
}
if (matched_index >= 0) {
elm.selectedIndex = matched_index;
func.runtime.bind.remember_select_numeric_context(elm, field_type, value);
return true;
}
return false;
};
func.runtime.bind.schedule_select_value_retry = function (elm, value, field_type, scheduled_at) {
if (elm.__xuda_select_set_retry_timer_ids) {
for (let index = 0; index < elm.__xuda_select_set_retry_timer_ids.length; index++) {
clearTimeout(elm.__xuda_select_set_retry_timer_ids[index]);
}
}
const retry_delays = [0, 50, 250, 500];
elm.__xuda_select_set_retry_timer_ids = retry_delays.map(function (delay) {
return setTimeout(function () {
if (!elm.isConnected) {
return;
}
if (elm.__xuda_select_last_user_change_ts && elm.__xuda_select_last_user_change_ts > scheduled_at) {
return;
}
func.runtime.bind.apply_select_value_once(elm, value, field_type);
}, delay);
});
};
func.runtime.bind.set_select_value = function (elm, value, field_type) {
if (elm?.tagName?.toLowerCase?.() !== 'select') {
return false;
}
const options = Array.from(elm.options || []);
const string_value = value === null || typeof value === 'undefined' ? '' : String(value);
if (elm.multiple) {
const selected_values = Array.isArray(value)
? value.map(function (item) {
return String(item);
})
: [string_value];
options.forEach(function (option) {
option.selected = selected_values.includes(String(option.value));
});
return true;
}
const scheduled_at = Date.now();
func.runtime.bind.apply_select_value_once(elm, value, field_type);
func.runtime.bind.schedule_select_value_retry(elm, value, field_type, scheduled_at);
return true;
};
func.runtime.bind.get_select_numeric_value = function (elm, raw_value) {
const raw_numeric_value = func.runtime.bind.to_finite_number(raw_value);
if (raw_numeric_value !== null) {
return raw_numeric_value;
}
if (elm?.tagName?.toLowerCase?.() !== 'select') {
return raw_value;
}
const selected_option = elm.options?.[elm.selectedIndex];
const selected_option_number = func.runtime.bind.get_select_option_number(selected_option);
if (selected_option_number !== null) {
return selected_option_number;
}
const context = elm.__xuda_select_numeric_bind_context;
if (context && Number.isFinite(context.value) && Number.isFinite(context.selectedIndex) && elm.selectedIndex >= 0) {
return context.value + (elm.selectedIndex - context.selectedIndex);
}
if (elm.selectedIndex >= 0) {
return elm.selectedIndex + 1;
}
return raw_value;
};
func.runtime.bind.normalize_raw_value = function (elm, field_prop, raw_value) {
const field_type = func.runtime.bind.get_field_type(field_prop);
if (field_type === 'number') {
return func.runtime.bind.get_select_numeric_value(elm, raw_value);
}
return raw_value;
};
func.runtime.bind.track_pending_update = function (SESSION_ID, promise) {
const session_obj = SESSION_OBJ?.[SESSION_ID];
if (!session_obj || !promise?.finally) {
return promise;
}
if (!session_obj.pending_bind_updates) {
session_obj.pending_bind_updates = new Set();
}
const tracked_promise = promise.finally(function () {
session_obj.pending_bind_updates.delete(tracked_promise);
});
session_obj.pending_bind_updates.add(tracked_promise);
return tracked_promise;
};
func.runtime.bind.wait_for_pending_updates = async function (SESSION_ID) {
const pending_bind_updates = SESSION_OBJ?.[SESSION_ID]?.pending_bind_updates;
if (!pending_bind_updates?.size) {
return;
}
await Promise.allSettled(Array.from(pending_bind_updates));
};
func.runtime.bind.attach_live_text_listener = function (adapter_name, adapter, elm, handler) {
const node = func.runtime.bind.get_bind_value_node(elm);
if (!node?.addEventListener || typeof handler !== 'function') {
return false;
}
const listener_key = `__xuda_${adapter_name}_live_text_bind_listeners`;
const listener_state_key = `${listener_key}_state`;
const previous_listeners = node[listener_key];
if (previous_listeners) {
for (let index = 0; index < previous_listeners.length; index++) {
node.removeEventListener(previous_listeners[index].event_name, previous_listeners[index].listener);
}
}
if (node[listener_state_key]?.debounce_timer) {
clearTimeout(node[listener_state_key].debounce_timer);
}
const debounce_ms = func.runtime.bind.get_live_text_debounce_ms();
const state = {
debounce_timer: null,
last_value: adapter.getter ? adapter.getter.call(adapter, node) : node.value,
pending_event: null,
pending_value: adapter.getter ? adapter.getter.call(adapter, node) : node.value,
};
node[listener_state_key] = state;
const listener = function (event) {
const current_value = adapter.getter ? adapter.getter.call(adapter, node) : node.value;
if (xu_isEqual(current_value, state.pending_value)) {
return;
}
state.pending_event = event;
state.pending_value = current_value;
clearTimeout(state.debounce_timer);
state.debounce_timer = setTimeout(function () {
const next_value = adapter.getter ? adapter.getter.call(adapter, node) : node.value;
state.debounce_timer = null;
state.pending_value = next_value;
if (xu_isEqual(next_value, state.last_value)) {
return;
}
state.last_value = next_value;
return handler(state.pending_event);
}, debounce_ms);
};
const listeners = [];
for (const event_name of ['input', 'keyup']) {
node.addEventListener(event_name, listener);
listeners.push({ event_name, listener });
}
node[listener_key] = listeners;
return true;
};
func.runtime.bind.normalize_adapter = function (adapter, adapter_name = 'adapter') {
if (!func.runtime.bind.is_valid_adapter(adapter)) {
return adapter;
}
return {
getter: function (elm) {
return adapter.getter.call(adapter, func.runtime.bind.get_bind_value_node(elm) || elm);
},
setter: function (elm, value) {
return adapter.setter.call(adapter, func.runtime.bind.get_bind_value_node(elm) || elm, value);
},
listener: function (elm, handler) {
const node = func.runtime.bind.get_bind_value_node(elm) || elm;
if (func.runtime.bind.should_use_live_text_listener(node)) {
return func.runtime.bind.attach_live_text_listener(adapter_name, adapter, node, handler);
}
return adapter.listener.call(adapter, node, handler);
},
};
};
func.runtime.bind.get_native_adapter = function () {
const has_explicit_value = function (elm) {
return !!elm?.hasAttribute?.('value');
};
const get_listener_event = function (elm) {
const tag_name = elm?.tagName?.toLowerCase?.();
const type = (elm?.type || '').toLowerCase();
if (tag_name === 'select' || ['checkbox', 'radio'].includes(type)) {
return 'change';
}
return 'input';
};
return {
getter: function (elm) {
if (!elm) {
return undefined;
}
const tag_name = elm?.tagName?.toLowerCase?.();
const type = (elm?.type || '').toLowerCase();
if (tag_name === 'select' && elm.multiple) {
return Array.from(elm.options || [])
.filter(function (option) {
return option.selected;
})
.map(function (option) {
return option.value;
});
}
if (type === 'checkbox') {
return has_explicit_value(elm) ? elm.value : !!elm.checked;
}
if (type === 'radio') {
return elm.value;
}
return typeof elm.value !== 'undefined' ? elm.value : undefined;
},
setter: function (elm, value) {
if (!elm) {
return false;
}
const tag_name = elm?.tagName?.toLowerCase?.();
const type = (elm?.type || '').toLowerCase();
if (tag_name === 'select' && elm.multiple) {
const selected_values = Array.isArray(value)
? value.map(function (item) {
return String(item);
})
: [String(value)];
Array.from(elm.options || []).forEach(function (option) {
option.selected = selected_values.includes(String(option.value));
});
return true;
}
if (type === 'checkbox' || type === 'radio') {
return true;
}
if (typeof elm.value !== 'undefined') {
elm.value = value === null || typeof value === 'undefined' ? '' : String(value);
}
return true;
},
listener: function (elm, handler) {
if (!elm?.addEventListener || typeof handler !== 'function') {
return false;
}
const event_name = get_listener_event(elm);
const listener_key = '__xuda_native_bind_listener_' + event_name;
if (elm[listener_key]) {
elm.removeEventListener(event_name, elm[listener_key]);
}
elm.addEventListener(event_name, handler);
elm[listener_key] = handler;
return true;
},
};
};
func.runtime.bind.is_valid_adapter = function (adapter) {
return !!(
adapter &&
typeof adapter.getter === 'function' &&
typeof adapter.setter === 'function' &&
typeof adapter.listener === 'function'
);
};
func.runtime.bind.get_adapter = function (SESSION_ID) {
const native_adapter = func.runtime.bind.get_native_adapter();
if (func.runtime.session.is_slim(SESSION_ID)) {
return func.runtime.bind.normalize_adapter(native_adapter, 'native');
}
const plugin_bind = UI_FRAMEWORK_PLUGIN?.bind;
if (!plugin_bind) {
return func.runtime.bind.normalize_adapter(native_adapter, 'native');
}
if (func.runtime.bind.is_valid_adapter(plugin_bind)) {
return func.runtime.bind.normalize_adapter(plugin_bind, 'plugin');
}
if (typeof plugin_bind === 'function') {
try {
const bind_instance = new plugin_bind();
if (func.runtime.bind.is_valid_adapter(bind_instance)) {
return func.runtime.bind.normalize_adapter(bind_instance, 'plugin');
}
} catch (error) {}
try {
const bind_factory = plugin_bind();
if (func.runtime.bind.is_valid_adapter(bind_factory)) {
return func.runtime.bind.normalize_adapter(bind_factory, 'plugin');
}
} catch (error) {}
}
return func.runtime.bind.normalize_adapter(native_adapter, 'native');
};
func.runtime.bind.resolve_field = async function (SESSION_ID, prog_id, dsSessionP, field_id, iterate_info) {
let _prog_id = prog_id;
let _dsP = dsSessionP;
let is_dynamic_field = false;
let field_prop;
const find_in_view = async function (field_id, prog_id) {
const view_ret = await func.utils.VIEWS_OBJ.get(SESSION_ID, prog_id);
if (!view_ret) {
return null;
}
return func.common.find_item_by_key(view_ret.progFields, 'field_id', field_id);
};
if (['_FOR_VAL', '_FOR_KEY'].includes(field_id)) {
is_dynamic_field = true;
if (iterate_info && (iterate_info.iterator_val === field_id || iterate_info.iterator_key === field_id)) {
const iter_value = iterate_info.iterator_val === field_id ? iterate_info._val : iterate_info._key;
const toType = function (obj) {
return {}.toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
};
field_prop = {
id: field_id,
data: { type: 'virtual', field_id },
props: { fieldType: typeof iter_value !== 'undefined' ? toType(iter_value) : 'string' },
value: iter_value,
};
} else {
field_prop = SESSION_OBJ[SESSION_ID]?.DS_GLB?.[_dsP]?.dynamic_fields?.[field_id];
}
} else {
field_prop = await find_in_view(field_id, _prog_id);
if (!field_prop) {
const ret_get_value = await func.datasource.get_value(SESSION_ID, field_id, _dsP);
if (ret_get_value.found) {
_dsP = ret_get_value.dsSessionP;
let _ds = SESSION_OBJ[SESSION_ID].DS_GLB[_dsP];
_prog_id = _ds?.prog_id;
field_prop = await find_in_view(field_id, _prog_id);
if (!field_prop) {
field_prop = _ds?.dynamic_fields?.[field_id];
if (field_prop) {
is_dynamic_field = true;
}
}
}
}
}
if (!field_prop) {
throw `field ${field_id} not found in the program scope`;
}
if (!is_dynamic_field) {
const _ds = SESSION_OBJ[SESSION_ID]?.DS_GLB?.[_dsP];
const table_id = _ds?._dataSourceTableId;
if (table_id) {
try {
const table_obj = await func.utils.FILES_OBJ.get(SESSION_ID, table_id);
const table_field_prop = func.common.find_item_by_key(table_obj?.tableFields || [], 'field_id', field_id);
const table_field_type = table_field_prop?.props?.fieldType;
if (table_field_type) {
field_prop = {
...field_prop,
props: {
...(field_prop.props || {}),
fieldType: table_field_type,
},
};
}
} catch (error) {}
}
}
return {
bind_field_id: field_id,
field_prop,
is_dynamic_field,
dsSessionP: _dsP,
prog_id: _prog_id,
};
};
func.runtime.bind.get_field_type = function (field_prop) {
return field_prop?.props?.fieldType;
};
func.runtime.bind.toggle_array_value = function (arr_value_before_cast, value_from_getter) {
if (arr_value_before_cast.includes(value_from_getter)) {
return arr_value_before_cast.filter((item) => !xu_isEqual(item, value_from_getter));
}
arr_value_before_cast.push(value_from_getter);
return arr_value_before_cast;
};
func.runtime.bind.get_cast_value = async function (SESSION_ID, field_prop, input_field_type, raw_value) {
const field_type = func.runtime.bind.get_field_type(field_prop);
if (field_type === 'object') {
return await func.common.get_cast_val(SESSION_ID, 'xu-bind', 'value', input_field_type, raw_value);
}
return await func.common.get_cast_val(SESSION_ID, 'xu-bind', 'value', field_type, raw_value);
};
func.runtime.bind.get_source_value = function (_ds, bind_field_id, is_dynamic_field) {
if (is_dynamic_field) {
return _ds.dynamic_fields[bind_field_id].value;
}
const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
return _ds.data_feed.rows?.[row_idx]?.[bind_field_id];
};
func.runtime.bind.format_display_value = function ($elm, field_prop, bind_field_id, expression_value, value, input_field_type) {
const field_type = func.runtime.bind.get_field_type(field_prop);
const elm_value = func.runtime.ui.get_attr($elm, 'value');
if (field_type === 'array' && input_field_type === 'checkbox' && elm_value) {
return value.includes(elm_value);
}
if (field_type === 'array' && input_field_type === 'radio' && elm_value) {
if (value.includes(elm_value)) {
return elm_value;
}
return false;
}
if (field_type === 'object' && expression_value.split('.').length > 1) {
let str = expression_value.replace(bind_field_id, '(' + JSON.stringify(value) + ')');
return eval(str);
}
return value;
};
func.runtime.bind.update_reference_source_array = async function (options) {
const field_type = func.runtime.bind.get_field_type(options.field_prop);
const reference_source_obj = options.iterate_info?.reference_source_obj;
if (!reference_source_obj || reference_source_obj.ret.type !== 'array' || options.iterate_info?.iterator_val !== options.bind_field_id) {
return false;
}
const arr_idx = Number(options.iterate_info._key);
const dataset_arr = await func.datasource.get_value(options.SESSION_ID, reference_source_obj.fieldIdP, options.dsSessionP, reference_source_obj.currentRecordId);
let new_arr = structuredClone(dataset_arr.ret.value);
if (field_type === 'object' && options.val_is_reference_field) {
let obj_item = new_arr[arr_idx];
let e_exp = options.expression_value.replace(options.bind_field_id, 'obj_item');
eval(e_exp + `=${JSON.stringify(options.value)}`);
new_arr[arr_idx] = obj_item;
} else {
new_arr[arr_idx] = options.value;
}
let datasource_changes = func.runtime.bind.build_datasource_changes(options.dsSessionP, options.currentRecordId, reference_source_obj.fieldIdP, new_arr);
await func.datasource.update(options.SESSION_ID, datasource_changes);
return true;
};
func.runtime.resources.load_cdn = async function (SESSION_ID, resource) {
let normalized_resource = resource;
if (!(typeof normalized_resource === 'object' && normalized_resource !== null) && typeof normalized_resource === 'string') {
normalized_resource = { src: normalized_resource, type: 'js' };
}
if (!(typeof normalized_resource === 'object' && normalized_resource !== null)) {
throw new Error('cdn resource in wrong format');
}
return new Promise(async (resolve) => {
try {
switch (normalized_resource.type) {
case 'js':
await func.utils.load_js_on_demand(normalized_resource.src);
break;
case 'css':
func.runtime.platform.load_css(normalized_resource.src);
break;
case 'module':
await func.utils.load_js_on_demand(normalized_resource.src, 'module');
break;
default:
await func.utils.load_js_on_demand(normalized_resource.src);
break;
}
resolve();
} catch (error) {
func.utils.debug_report(SESSION_ID, 'xu-cdn', 'Fail to load: ' + normalized_resource, 'W');
resolve();
}
});
};
func.runtime.resources.get_plugin_manifest_entry = function (_session, plugin_name) {
return APP_OBJ[_session.app_id]?.app_plugins_purchased?.[plugin_name] || null;
};
func.runtime.resources.get_plugin_resource_candidates = function (_session, plugin, resource) {
const manifest_entry = plugin?.manifest?.[resource];
const default_path = `${manifest_entry?.dist ? 'dist/' : ''}${resource}`;
const candidates = [];
if (_session?.worker_type === 'Dev' && manifest_entry?.dist && /\.mjs$/.test(resource)) {
candidates.push(`src/${resource}`);
}
candidates.push(default_path);
return Array.from(new Set(candidates.filter(Boolean)));
};
func.runtime.resources.get_plugin_module_path = function (plugin, resource, _session) {
return func.runtime.resources.get_plugin_resource_candidates(_session, plugin, resource)[0] || resource;
};
func.runtime.resources.get_plugin_module_url = async function (SESSION_ID, plugin_name, plugin, resource) {
const _session = SESSION_OBJ[SESSION_ID];
return await func.utils.get_plugin_npm_cdn(SESSION_ID, plugin_name, func.runtime.resources.get_plugin_module_path(plugin, resource, _session));
};
func.runtime.resources.import_plugin_module = async function (SESSION_ID, plugin_name, plugin, resource) {
const _session = SESSION_OBJ[SESSION_ID];
const candidates = func.runtime.resources.get_plugin_resource_candidates(_session, plugin, resource);
let last_error = null;
for (let index = 0; index < candidates.length; index++) {
const candidate = candidates[index];
try {
return await func.utils.get_plugin_resource(SESSION_ID, plugin_name, candidate);
} catch (error) {
last_error = error;
}
}
throw last_error || new Error(`plugin resource not found: ${plugin_name}/${resource}`);
};
func.runtime.resources.load_plugin_runtime_css = async function (SESSION_ID, plugin_name, plugin) {
if (!plugin?.manifest?.['runtime.mjs']?.dist || !plugin?.manifest?.['runtime.mjs']?.css) {
return false;
}
const plugin_runtime_css_url = await func.utils.get_plugin_npm_cdn(SESSION_ID, plugin_name, 'dist/runtime.css');
func.utils.load_css_on_demand(plugin_runtime_css_url);
return true;
};
func.runtime.resources.resolve_plugin_properties = async function (SESSION_ID, dsSessionP, attributes, properties) {
// Plugin property schemas can carry function-valued defaults (e.g. tippy
// `placement: () => "top"`), which structuredClone cannot clone and throws on,
// breaking every element that uses the plugin. xu_cloneDeep tries
// structuredClone first and falls back to a recursive clone that preserves
// function references, so the schema clones cleanly either way.
let resolved_properties = xu_cloneDeep(properties);
for await (let [prop_name, prop_val] of Object.entries(resolved_properties || {})) {
prop_val.value = attributes?.[prop_name];
if (attributes?.[`xu-exp:${prop_name}`]) {
const res = await func.expression.get(SESSION_ID, attributes[`xu-exp:${prop_name}`], dsSessionP, 'UI Attr EXP');
prop_val.value = res.result;
}
}
return resolved_properties;
};
func.runtime.resources.run_ui_plugin = async function (SESSION_ID, paramsP, $elm, plugin_name, value) {
var _session = SESSION_OBJ[SESSION_ID];
const plugin = func.runtime.resources.get_plugin_manifest_entry(_session, plugin_name);
if (!plugin?.installed || !plugin?.manifest?.['runtime.mjs']?.exist || !plugin?.manifest?.['index.mjs']?.exist || !value?.enabled) {
return false;
}
await func.runtime.resources.load_plugin_runtime_css(SESSION_ID, plugin_name, plugin);
const plugin_index_resources = await func.runtime.resources.import_plugin_module(SESSION_ID, plugin_name, plugin, 'index.mjs');
const properties = await func.runtime.resources.resolve_plugin_properties(SESSION_ID, paramsP.dsSessionP, value?.attributes, plugin_index_resources.properties);
const plugin_runtime_resources = await func.runtime.resources.import_plugin_module(SESSION_ID, plugin_name, plugin, 'runtime.mjs');
if (plugin_runtime_resources.cdn && Array.isArray(plugin_runtime_resources.cdn)) {
for await (const resource of plugin_runtime_resources.cdn) {
await func.runtime.resources.load_cdn(SESSION_ID, resource);
}
}
if (plugin_runtime_resources.fn) {
const plugin_element = func.runtime.ui.get_first_node?.($elm) || $elm?.[0] || $elm;
if (!plugin_element) {
return false;
}
await plugin_runtime_resources.fn(plugin_name, plugin_element, properties);
}
return true;
};
func.runtime.widgets.create_context = function (SESSION_ID, paramsP, prop) {
const _session = SESSION_OBJ[SESSION_ID];
const plugin_name = prop['xu-widget'];
return {
SESSION_ID,
_session,
plugin_name,
method: prop['xu-method'] || '_default',
dsP: paramsP.dsSessionP,
propsP: prop,
sourceP: 'widgets',
plugin: APP_OBJ[_session.app_id]?.app_plugins_purchased?.[plugin_name] || null,
};
};
func.runtime.widgets.report_error = function (context, descP, warn) {
const program = context?._session?.DS_GLB?.[context.dsP];
if (!program) {
return null;
}
func.utils.debug.log(context.SESSION_ID, program.prog_id + '_' + program.callingMenuId, {
module: 'widgets',
action: 'Init',
source: context.sourceP,
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'widgets',
prog_id: program.prog_id,
});
return null;
};
func.runtime.widgets.get_property_value = async function (context, fieldIdP, val, props) {
if (!val) return;
var value = fieldIdP in props ? props[fieldIdP] : typeof val.defaultValue === 'function' ? val?.defaultValue?.() : val?.defaultValue;
if (val.render === 'eventId') {
value = props?.[fieldIdP]?.event;
}
if (props[`xu-exp:${fieldIdP}`]) {
value = (await func.expression.get(context.SESSION_ID, props[`xu-exp:${fieldIdP}`], context.dsP, 'widget property')).result;
}
return func.common.get_cast_val(
context.SESSION_ID,
'widgets',
fieldIdP,
val.type,
value,
null,
);
};
func.runtime.widgets.get_fields_data = async function (context, fields, props) {
var data_obj = {};
var return_code = 1;
for await (const [key, val] of Object.entries(fields || {})) {
data_obj[key] = await func.runtime.widgets.get_property_value(context, key, val, props);
if (!data_obj[key] && val.mandatory) {
return_code = -1;
func.runtime.widgets.report_error(context, `${key} is a mandatory field.`);
break;
}
}
for await (const key of ['xu-bind']) {
data_obj[key] = await func.runtime.widgets.get_property_value(context, key, props?.[key], props);
}
return { code: return_code, data: data_obj };
};
func.runtime.widgets.get_resource_candidates = function (context, resource) {
return func.runtime.resources.get_plugin_resource_candidates(context._session, context.plugin, resource);
};
func.runtime.widgets.normalize_capabilities = function (definition) {
const capabilities = definition?.capabilities || {};
return {
browser: capabilities.browser !== false,
headless: capabilities.headless === true,
};
};
func.runtime.widgets.supports_current_environment = function (definition) {
const capabilities = func.runtime.widgets.normalize_capabilities(definition);
if (func.runtime.platform.has_document()) {
return capabilities.browser !== false;
}
return !!capabilities.headless;
};
func.runtime.widgets.get_resource_path = function (context, resource) {
const relative_path = func.runtime.widgets.get_resource_candidates(context, resource)[0] || resource;
const server_origin = typeof globalThis !== 'undefined' ? globalThis.__XU_SERVER_ORIGIN__ : '';
if (server_origin) {
return `${server_origin}/plugins/${context.plugin_name}/${relative_path}?gtp_token=${context._session.gtp_token}&app_id=${context._session.app_id}`;
}
if (context._session.worker_type === 'Dev') {
return `../../plugins/${context.plugin_name}/${relative_path}`;
}
return `https://${context._session.domain}/plugins/${context.plugin_name}/${relative_path}?gtp_token=${context._session.gtp_token}&app_id=${context._session.app_id}`;
};
func.runtime.widgets.load_css_style = function (context) {
func.utils.load_css_on_demand(func.runtime.widgets.get_resource_path(context, 'style.css'));
return true;
};
func.runtime.widgets.get_resource = async function (context, resource) {
const candidates = func.runtime.widgets.get_resource_candidates(context, resource);
let last_error = null;
for (let index = 0; index < candidates.length; index++) {
const candidate = candidates[index];
try {
return await func.utils.get_plugin_resource(context.SESSION_ID, context.plugin_name, candidate);
} catch (error) {
last_error = error;
}
}
throw last_error || new Error(`widget resource not found: ${context.plugin_name}/${resource}`);
};
func.runtime.widgets.get_definition = async function (context) {
return await func.runtime.widgets.get_resource(context, 'index.mjs');
};
func.runtime.widgets.get_methods = async function (context) {
const index = await func.runtime.widgets.get_definition(context);
return index?.methods || {};
};
func.runtime.widgets.load_runtime_css = async function (context) {
if (!context.plugin?.manifest?.['runtime.mjs']?.dist || !context.plugin?.manifest?.['runtime.mjs']?.css) {
return false;
}
const plugin_runtime_css_url = await func.utils.get_plugin_npm_cdn(context.SESSION_ID, context.plugin_name, 'dist/runtime.css');
func.utils.load_css_on_demand(plugin_runtime_css_url);
return true;
};
func.runtime.widgets.build_params = function (context, container_node, container_data, plugin_setup, api_utils, extra = {}) {
return {
SESSION_ID: context.SESSION_ID,
method: context.method,
_session: context._session,
dsP: context.dsP,
sourceP: context.sourceP,
propsP: context.propsP,
plugin_name: context.plugin_name,
container_node,
container_data,
plugin_setup,
report_error: function (descP, warn) {
return func.runtime.widgets.report_error(context, descP, warn);
},
log_error: function (descP, warn) {
return func.runtime.widgets.report_error(context, descP, warn);
},
call_plugin_api: async function (plugin_nameP, dataP) {
return await func.utils.call_plugin_api(context.SESSION_ID, plugin_nameP, dataP);
},
set_SYS_GLOBAL_OBJ_WIDGET_INFO: async function (docP) {
return await func.utils.set_SYS_GLOBAL_OBJ_WIDGET_INFO(context.SESSION_ID, docP);
},
run_widgetCallbackEvent: async function () {
const event_id = context.propsP?.widgetCallbackEvent;
if (!event_id || !api_utils?.invoke_event) {
return false;
}
return await api_utils.invoke_event(event_id);
},
api_utils,
...extra,
};
};
func.common.find_item_by_key = function (arr, key, val) {
return arr.find(function (e) {
return e.data[key] === val;
});
};
func.common.find_item_by_key_root = function (arr, key, val) {
return arr.find(function (e) {
return e[key] === val;
});
};
func.common.find_ROWID_idx = function (_ds, rowId) {
if (!_ds?.data_feed?.rows) {
throw new Error('data_feed not found');
}
// Find the index of the object with the given _ROWID
const index = _ds.data_feed.rows.findIndex((item) => item._ROWID === rowId);
// }
// If the index is -1, the ROWID was not found, so throw an error
if (index === -1) {
throw new Error(`ROWID "${rowId}" not found`);
}
// Return the found index
return index;
};
func.common.input_mask = async function (actionP, valP, typeP, maskP, elemP, grid_objP, grid_row_idP, grid_col_idP, dsSessionP) {
const module = await func.common.get_module(SESSION_ID, 'xuda-input-musk-utils-module.mjs');
module.input_mask(actionP, valP, typeP, maskP, elemP, grid_objP, grid_row_idP, grid_col_idP, dsSessionP);
};
glb.FUNCTION_NODES_ARR = ['batch', 'get_data', 'set_data', 'alert', 'javascript', 'api'];
glb.ALL_MENU_TYPE = ['globals', 'ai_agent', 'component', ...glb.FUNCTION_NODES_ARR];
glb.emailRegex = /^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$/;
const FIREBASE_AUTH_PROPERTIES_ARR = ['provider', 'token', 'first_name', 'last_name', 'email', 'user_id', 'picture', 'verified_email', 'locale', 'error_code', 'error_msg'];
const CLIENT_INFO_PROPERTIES_ARR = [
'fingerprint',
'device',
'user_agent',
'browser_version',
'browser_name',
'engine_version',
'engine_name',
'client_ip',
'os_name',
'os_version',
'device_model',
'device_vendor',
'device_type',
'screen_current_resolution_x',
'screen_current_resolution_y',
'screen_available_resolution_x',
'screen_available_resolution_y',
'language',
'time_zone',
'cpu_architecture',
'uuid',
'cursor_pos_x',
'cursor_pos_y',
];
const APP_PROPERTIES_ARR = ['build', 'author', 'date', 'name'];
const DATASOURCE_PROPERTIES_ARR = ['rows', 'type', 'first_row_id', 'last_row_id', 'query_from_segments_json', 'query_to_segments_json', 'locate_query_from_segments_json', 'locate_query_to_segments_json', 'first_row_segments_json', 'last_row_segments_json', 'rowid_snapshot', 'rowid'];
glb.MOBILE_ARR = ['component', 'web_app', 'ios_app', 'android_app', 'electron_app', 'osx_app', 'windows_app'];
glb.SYS_DATE_ARR = ['SYS_DATE', 'SYS_DATE_TIME', 'SYS_DATE_VALUE', 'SYS_DATE_WEEK_YEAR', 'SYS_DATE_MONTH_YEAR', 'SYS_TIME_SHORT', 'SYS_TIME'];
glb.API_OUTPUT_ARR = ['json', 'html', 'xml', 'text', 'css', 'javascript'];
const PROTECTED_NAMES_ARR = ['THIS', 'ROWID']; //tbd
func.common.db = async function (SESSION_ID, serviceP, dataP, opt = {}, dsSession) {
return new Promise(async function (resolve, reject) {
var _session = SESSION_OBJ[SESSION_ID];
const app_id = _session.app_id;
if (glb.DEBUG_MODE) {
console.info('request', dataP);
}
var data = {
app_id: app_id,
fingerprint: _session?.SYS_GLOBAL_OBJ_CLIENT_INFO?.fingerprint,
debug: glb.DEBUG_MODE,
session_id: SESSION_ID,
gtp_token: _session.gtp_token,
app_token: _session.app_token,
res_token: _session.res_token,
engine_mode: _session.engine_mode,
req_id: 'rt_req_' + crypto.randomUUID(),
app_replicate: APP_OBJ[app_id].app_replicate,
};
try {
if (typeof firebase !== 'undefined' && firebase?.auth()?.currentUser?.displayName) {
data.device_name = firebase.auth().currentUser.displayName;
}
} catch (error) {}
for (const [key, val] of Object.entries(dataP)) {
data[key] = val;
}
const success_callback = function (ret) {
if (dataP.table_id && DOCS_OBJ[app_id][dataP.table_id]) {
func.utils.debug.watch(SESSION_ID, dataP.table_id, 'table', DOCS_OBJ[app_id][dataP.table_id].properties.menuName, {
req: data,
res: ret,
});
}
if (glb.DEBUG_MODE) {
console.info('response', ret);
}
resolve(ret, true);
};
const error_callback = function (err) {
reject(err);
};
function cleanString(json) {
let str = JSON.stringify(json);
// Replace all non-alphanumeric characters with an empty string
return str.replace(/[^a-zA-Z0-9]/g, '');
}
const get_rep_id = function () {
let _data = {};
const fields_to_skip = ['fields', 'viewSourceDesc', 'skip', 'limit', 'count', 'reduce', 'prog_id', 'sortModel', 'filterModelMongo', 'filterModelSql', 'filterModelUserMongo', 'filterModelUserSql'];
for (let [key, val] of Object.entries(dataP)) {
if (typeof val !== 'undefined' && val !== null && !fields_to_skip.includes(key)) {
_data[key] = val;
}
}
return cleanString(_data);
};
const validate_existence_of_whole_table_request = async function (db) {
let table_req_id;
try {
table_req_id = cleanString({
key: data.table_id,
table_id: data.table_id,
});
const doc = await db.get(table_req_id);
let ret = await db.find({
selector: {
docType: 'rep_request',
table_id: data.table_id,
},
});
if (doc.stat < 3) {
throw 'not ready';
}
/// delete table refunded requests
for (let doc of ret.docs) {
if (doc.entire_table) continue;
func.db.pouch.remove_db_replication_from_server(SESSION_ID, doc._id);
}
return { code: 1, data: table_req_id };
} catch (err) {
return { code: -1, data: table_req_id };
}
};
const upsert_rep_request_from_remote_response = async function (db, rep_id, table_req_id, json) {
if (!json?.data?.opt) return;
try {
let existing_doc;
try {
existing_doc = await db.get(rep_id);
} catch (err) {}
const rep_doc = {
_id: rep_id,
selector: json.data.opt.selector,
stat: 1,
ts: Date.now(),
docType: 'rep_request',
table_id: dataP.table_id,
prog_id: dataP.prog_id,
entire_table: table_req_id === rep_id,
source: 'runtime',
e: data,
};
if (existing_doc?._rev) {
rep_doc._rev = existing_doc._rev;
}
await db.put(rep_doc);
func.db.pouch.set_db_replication_from_server(SESSION_ID);
} catch (err) {}
};
const read_remote_dbs = async function (db, rep_id, table_req_id) {
const json = await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
await upsert_rep_request_from_remote_response(db, rep_id, table_req_id, json);
return json;
};
const should_retry_live_preview_remote_read = function (json) {
return (
_session?.engine_mode === 'live_preview' &&
serviceP === 'dbs_read' &&
dataP.table_id &&
!dataP.count &&
!dataP.reduce &&
Array.isArray(json?.data?.rows) &&
!json.data.rows.length
);
};
const read_local_dbs_with_live_preview_fallback = async function (db, rep_id, table_req_id) {
const json = {
code: 1,
data: await func.db.pouch[serviceP](SESSION_ID, data),
};
if (should_retry_live_preview_remote_read(json)) {
return await read_remote_dbs(db, rep_id, table_req_id);
}
return json;
};
const read_dbs_pouch = async function (db) {
if (_session?.DS_GLB?.[dsSession]?.refreshed && (dataP.filterModelMongo || dataP.filterModelSql)) {
return await read_local_dbs_with_live_preview_fallback(db, get_rep_id(), null);
}
const rep_id = get_rep_id();
const { code: table_req_code, data: table_req_id } = await validate_existence_of_whole_table_request(db);
if (table_req_code > 0) {
return await read_local_dbs_with_live_preview_fallback(db, rep_id, table_req_id);
}
try {
const doc = await db.get(rep_id);
if (doc.stat < 3) throw 'replication not ready';
return await read_local_dbs_with_live_preview_fallback(db, rep_id, table_req_id);
} catch (err) {
return await read_remote_dbs(db, rep_id, table_req_id);
}
};
const update_dbs_pouch = async function (db) {
try {
// if (_session.DS_GLB[0].data_system.SYS_GLOBAL_BOL_ONLINE) {
// throw "online";
// }
const { code: table_req_code, data: table_req_id } = await validate_existence_of_whole_table_request(db);
if (table_req_code > 0) {
data.full_table_downloaded = true;
}
await db.get(dataP.row_id);
return await func.db.pouch[serviceP](SESSION_ID, data);
} catch (err) {
return await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
}
};
const create_dbs_pouch = async function (db) {
try {
const { code: table_req_code, data: table_req_id } = await validate_existence_of_whole_table_request(db);
if (table_req_code > 0) {
data.full_table_downloaded = true;
}
return await func.db.pouch[serviceP](SESSION_ID, data);
} catch (err) {
return await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
}
};
const delete_dbs_pouch = async function (db) {
for await (let row_id of dataP.ids || []) {
try {
const { code: table_req_code, data: table_req_id } = await validate_existence_of_whole_table_request(db);
if (table_req_code > 0) {
data.full_table_downloaded = true;
}
await db.get(row_id);
let _data = structuredClone(dataP);
_data.ids = [row_id];
return await func.db.pouch['dbs_delete'](SESSION_ID, _data);
} catch (err) {
return await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
}
}
};
if (typeof IS_DOCKER === 'undefined' && typeof IS_PROCESS_SERVER === 'undefined') {
try {
if (!SESSION_OBJ?.[SESSION_ID]?.rpi_http_methods?.includes(serviceP)) {
throw '';
}
const is_local_draft_pouch_runtime =
['miniapp', 'live_preview'].includes(_session?.engine_mode) &&
_session?.is_draft_runtime &&
Array.isArray(_session?.rpi_http_methods) &&
_session.rpi_http_methods.includes('dbs_read');
if (is_local_draft_pouch_runtime && ['dbs_read', 'dbs_update', 'dbs_create', 'dbs_delete'].includes(serviceP)) {
return success_callback({
code: 1,
data: await func.db.pouch[serviceP](SESSION_ID, data),
});
}
if (!(await func?.db?.pouch?.get_replication_stat(SESSION_ID))) throw '';
const db = await func.utils.connect_pouchdb(SESSION_ID);
if (_session?.engine_mode === 'live_preview' && !_session?.is_draft_runtime && ['dbs_update', 'dbs_create', 'dbs_delete'].includes(serviceP)) {
const json = await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
return success_callback(json, true);
}
switch (serviceP) {
case 'dbs_read': {
try {
return success_callback(await read_dbs_pouch(db));
} catch (err) {
if (err === 'creating index in progress') {
throw '';
}
return error_callback(err);
}
break;
}
case 'dbs_update': {
try {
const ret = {
code: 1,
data: await update_dbs_pouch(db),
};
return success_callback(ret);
} catch (err) {
return error_callback(err);
}
break;
}
case 'dbs_create': {
try {
const ret = {
code: 1,
data: await create_dbs_pouch(db),
};
return success_callback(ret);
} catch (err) {
return error_callback(err);
}
break;
}
case 'dbs_delete': {
try {
const ret = {
code: 1,
data: await delete_dbs_pouch(db),
};
return success_callback(ret);
} catch (err) {
return error_callback(err);
}
break;
}
default:
throw '';
break;
}
} catch (err) {
try {
const json = await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
return success_callback(json, true);
} catch (err) {
return error_callback(err);
}
}
}
// DOCKER // PROCESS_SERVER
const response = function (res, ret) {
if (ret.code < 0) {
return error_callback(ret);
}
success_callback(ret);
};
const get_white_spaced_data = function (data) {
var e = {};
for (const [key, val] of Object.entries(data)) {
if (!val) {
if (typeof val === 'boolean') {
e[key] = 'false';
} else {
e[key] = '';
}
} else {
if (typeof val === 'boolean') {
e[key] = 'true';
} else {
e[key] = val;
}
}
}
if (data.fields && !data.fields.length) {
e.fields = '';
}
return e;
};
if (dataP.table_id) {
await func.utils.FILES_OBJ.get(SESSION_ID, dataP.table_id);
await func.utils.TREE_OBJ.get(SESSION_ID, dataP.table_id);
}
data.db_driver = 'xuda';
__.rpi.http_calls(serviceP, { body: get_white_spaced_data(data) }, null, response);
});
};
func.common.getJsonFromUrl = function () {
return func.runtime.env.get_url_params();
};
func.common.getParametersFromUrl = function () {
return func.runtime.env.get_url_parameters_object();
};
func.common.getObjectFromUrl = function (url, element_attributes_obj, embed_params_obj) {
var result = {};
if (element_attributes_obj) {
for (let [key, val] of Object.entries(element_attributes_obj)) {
result[key] = val;
}
}
if (embed_params_obj) {
for (let [key, val] of Object.entries(embed_params_obj)) {
result[key] = val;
}
}
if (!url && typeof IS_DOCKER === 'undefined' && typeof IS_PROCESS_SERVER === 'undefined') {
url = location.href;
}
var question = url.indexOf('?');
var hash = url.indexOf('#');
if (hash == -1 && question == -1) return result;
if (hash == -1) hash = url.length;
var query = question == -1 || hash == question + 1 ? url.substring(hash) : url.substring(question + 1, hash);
// var result = {};
query.split('&').forEach(function (part) {
if (!part) return;
part = part.split('+').join(' '); // replace every + with space, regexp-free version
var eq = part.indexOf('=');
var key = eq > -1 ? part.substr(0, eq) : part;
var val = eq > -1 ? decodeURIComponent(part.substr(eq + 1)) : '';
var from = key.indexOf('[');
if (from == -1) {
result[decodeURIComponent(key)] = val;
} else {
var to = key.indexOf(']', from);
var index = decodeURIComponent(key.substring(from + 1, to));
key = decodeURIComponent(key.substring(0, from));
if (!result[key]) result[key] = [];
if (!index) result[key].push(val);
else result[key][index] = val;
}
});
return result;
};
func.common.getContrast_color = function (hexcolor) {
function colourNameToHex(colour) {
var colours = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
'indianred ': '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgrey: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370d8',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#d87093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#ff0000',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32',
};
if (typeof colours[colour.toLowerCase()] != 'undefined') return colours[colour.toLowerCase()];
return false;
}
if (!hexcolor.includes('#')) {
hexcolor = colourNameToHex(hexcolor);
}
// If a leading # is provided, remove it
if (hexcolor.slice(0, 1) === '#') {
hexcolor = hexcolor.slice(1);
}
// Convert to RGB value
var r = Number(hexcolor.substr(0, 2), 16);
var g = Number(hexcolor.substr(2, 2), 16);
var b = Number(hexcolor.substr(4, 2), 16);
// Get YIQ ratio
var yiq = (r * 299 + g * 587 + b * 114) / 1000;
// Check contrast
return yiq >= 128 ? 'black' : 'white';
};
func.common.get_url = function (SESSION_ID, method, path) {
const _session = SESSION_OBJ[SESSION_ID] || {};
const origin =
((typeof globalThis !== 'undefined' && globalThis.__XU_SERVER_ORIGIN__)) ||
(_session.domain ? `https://${_session.domain}` : '');
if (!origin) {
return `/${method}${path ? '/' + path : '/'}`;
}
return `${origin}/${method}${path ? '/' + path : '/'}`;
};
var UI_FRAMEWORK_INSTALLED = null;
var UI_FRAMEWORK_PLUGIN = {};
func.common.get_cast_val = async function (SESSION_ID, source, attributeP, typeP, valP, errorP) {
const report_conversion_error = function (res) {
if (errorP) {
return func.utils.debug_report(SESSION_ID, source.charAt(0).toUpperCase() + source.slice(1).toLowerCase(), errorP, 'W');
}
var msg = `error converting ${attributeP} from ${valP} to ${typeP}`;
func.utils.debug_report(SESSION_ID, source.charAt(0).toUpperCase() + source.slice(1).toLowerCase(), msg, 'E');
};
const report_conversion_warn = function (msg) {
// number/boolean/bigint -> string is a lossless coercion (String(v) is always exact); don't surface
// it as a runtime warning — it routes through report_issue and shows as an "Unhandled Runtime Error".
if (typeP === 'string' && (typeof valP === 'number' || typeof valP === 'boolean' || typeof valP === 'bigint')) return;
var msg = `type mismatch auto conversion made to ${attributeP} from value ${valP} to ${typeP}`;
func.utils.debug_report(SESSION_ID, source.charAt(0).toUpperCase() + source.slice(1).toLowerCase(), msg, 'W');
};
const module = await func.common.get_module(SESSION_ID, `xuda-get-cast-util-module.mjs`);
return module.cast(typeP, valP, report_conversion_error, report_conversion_warn);
};
var WEB_WORKER = {};
var WEB_WORKER_CALLBACK_QUEUE = {};
glb.DEBUG_MODE = null;
var DS_UI_EVENTS_GLB = {};
var RUNTIME_SERVER_WEBSOCKET = null;
var RUNTIME_SERVER_WEBSOCKET_CONNECTED = null;
var WEBSOCKET_PROCESS_PID = null;
glb.worker_queue_num = 0;
glb.websocket_queue_num = 0;
func.common._import_cache = func.common._import_cache || {};
func.common.get_module = async function (SESSION_ID, module, paramsP = {}) {
let ret;
const get_ret = async function (src) {
// Cache the import() result to avoid repeated dynamic imports
if (!func.common._import_cache[src]) {
func.common._import_cache[src] = await import(src);
}
const module_ret = func.common._import_cache[src];
var params = get_params();
const ret = module_ret.XudaModule ? new module_ret.XudaModule(params) : await invoke_init_module(module_ret, params);
return ret;
};
const get_params = function () {
let params = {
glb,
func,
APP_OBJ,
SESSION_ID,
PROJECT_OBJ,
DOCS_OBJ,
SESSION_OBJ,
...paramsP,
};
if (typeof IS_PROCESS_SERVER !== 'undefined') params.IS_PROCESS_SERVER = IS_PROCESS_SERVER;
if (typeof IS_API_SERVER !== 'undefined') params.IS_API_SERVER = IS_API_SERVER;
if (typeof IS_DOCKER !== 'undefined') params.IS_DOCKER = IS_DOCKER;
return params;
};
const invoke_init_module = async function (module_ret, params) {
if (!module_ret.init_module) return module_ret;
await module_ret.init_module(params);
return module_ret;
};
const _session = SESSION_OBJ[SESSION_ID];
const append_ts = function (resource_path) {
const is_debug_live_runtime = ['Dev', 'Debug'].includes(_session?.worker_type) && ['live_preview', 'miniapp'].includes(_session?.engine_mode);
let local_runtime_cache_tag =
typeof globalThis !== 'undefined'
? globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ || globalThis.__XU_SERVER_BOOTSTRAP__?.version
: 0;
if (is_debug_live_runtime && typeof globalThis !== 'undefined') {
globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ = globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ || Date.now();
local_runtime_cache_tag = globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__;
}
const cache_tag =
local_runtime_cache_tag ||
_session?.build_info?.runtime_ts ||
_session?.build_info?.last_changed_ts ||
_session?.build_info?.server_ts ||
_session?.opt?.app_build_id ||
0;
if (!cache_tag) {
return resource_path;
}
return `${resource_path}${resource_path.includes('?') ? '&' : '?'}ts=${cache_tag}`;
};
if (_session.worker_type === 'Dev') {
ret = await get_ret(append_ts('./modules/' + module));
return ret;
}
if (_session.worker_type === 'Debug') {
if (typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
ret = await get_ret(func.utils.get_resource_filename(['live_preview', 'miniapp'].includes(_session.engine_mode) ? '' : _session?.opt?.app_build_id, `${_conf.xuda_home}root/dist/runtime/js/modules/` + module));
} else {
ret = await get_ret(
append_ts(
func.common.get_url(
SESSION_ID,
'dist',
func.utils.get_resource_filename(['live_preview', 'miniapp'].includes(_session.engine_mode) ? '' : _session?.opt?.app_build_id, 'runtime/js/modules/' + module),
),
),
);
}
return ret;
}
const rep = function () {
return module.endsWith('.js') ? module.replace('.js', '.min.js') : module.replace('.mjs', '.min.mjs');
};
if (typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
ret = await get_ret(func.utils.get_resource_filename(['live_preview', 'miniapp'].includes(_session.engine_mode) ? '' : _session?.opt?.app_build_id, `${_conf.xuda_home}root/dist/runtime/js/modules/` + rep()));
} else {
ret = await get_ret(
append_ts(
func.common.get_url(
SESSION_ID,
'dist',
func.utils.get_resource_filename(['live_preview', 'miniapp'].includes(_session.engine_mode) ? '' : _session?.opt?.app_build_id, 'runtime/js/modules/' + rep()),
),
),
);
}
return ret;
};
func.api = {};
func.api.set_field_value = async function (field_id, value, avoid_refresh) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.set_field_value(field_id, value, avoid_refresh);
};
func.api.get_field_value = async function (field_id) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.get_field_value(field_id);
};
func.api.invoke_event = async function (event_id, options) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.invoke_event(event_id, options);
};
func.api.call_project_api = async function (prog_id, params) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.call_project_api(prog_id, params, null);
};
func.api.call_system_api = async function (api_method, payload) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.call_system_api(api_method, payload, null);
};
func.api.dbs_create = async function (table_id, data, cb) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.dbs_create(table_id, row_id, data, cb);
};
func.api.dbs_read = async function (table_id, selector, fields, sort, limit, skip, cb) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.dbs_read(table_id, selector, fields, sort, limit, skip, cb);
};
func.api.dbs_update = async function (table_id, row_id, data, cb) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.dbs_update(table_id, row_id, data, cb);
};
func.api.dbs_delete = async function (table_id, row_id, cb) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.dbs_delete(table_id, row_id, cb);
};
func.api.call_javascript = async function (prog_id, params, evaluate) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.call_javascript(prog_id, params, evaluate);
};
func.api.watch = function (path, cb, opt = {}) {
if (!path) return 'path is mandatory';
if (!cb) return 'cb (callback function) is mandatory';
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
let _session = SESSION_OBJ[SESSION_ID];
if (!_session.watchers) {
_session.watchers = {};
}
_session.watchers[path] = { ...opt, handler: cb };
if (opt.immediate) {
const value = xu_get(SESSION_OBJ[SESSION_ID].DS_GLB[0], path);
cb({ path, newValue: value, oldValue: value, timestamp: Date.now(), opt });
if (opt.once) {
delete _session.watchers[path];
}
}
return 'ok';
};
// func.api.call_javascript = async function (prog_id, params, evaluate) {
// const SESSION_ID = Object.keys(SESSION_OBJ)[0];
// const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
// func,
// glb,
// SESSION_OBJ,
// SESSION_ID,
// APP_OBJ,
// dsSession: func.utils.get_last_datasource_no(SESSION_ID),
// });
// return await api_utils.call_javascript(prog_id, params, evaluate);
// };
glb.rpi_request_queue_num = 0;
func.common.perform_rpi_request = async function (SESSION_ID, serviceP, opt = {}, data) {
var _session = SESSION_OBJ[SESSION_ID];
var _data_system = _session?.DS_GLB?.[0]?.data_system;
const set_ajax = async function (stat) {
var datasource_changes = {
[0]: {
['data_system']: { SYS_GLOBAL_BOL_AJAX_BUSY: stat },
},
};
await func.datasource.update(SESSION_ID, datasource_changes);
};
if (_data_system) {
// _data_system.SYS_GLOBAL_BOL_AJAX_BUSY = 1;
await set_ajax(1);
if (!_data_system.SYS_GLOBAL_BOL_CONNECTED) {
func.utils.alerts.toast(SESSION_ID, 'Server connection error', 'You are not connected to the server, so your request cannot be processed.', 'error');
return { code: 88, data: {} };
}
}
const http = async function () {
const fetchWithTimeout = (url, options = {}, timeout = 600000) => {
// 100 seconds
const controller = new AbortController();
const { signal } = controller;
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => {
controller.abort();
reject(new Error('Request timed out'));
}, timeout),
);
const fetchPromise = fetch(url, { ...options, signal });
return Promise.race([fetchPromise, timeoutPromise]);
};
var url = func.common.get_url(SESSION_ID, 'rpi', '');
var _session = SESSION_OBJ[SESSION_ID];
const app_id = _session.app_id;
if (APP_OBJ[app_id].is_deployment && _session.rpi_http_methods?.includes(serviceP)) {
const origin =
((typeof globalThis !== 'undefined' && globalThis.__XU_SERVER_ORIGIN__)) ||
(_session.host ? 'https://' + _session.host : '');
url = origin ? origin + '/rpi/' : url;
}
url += serviceP;
try {
const response = await fetchWithTimeout(url, {
method: opt.type ? opt.type : 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'xu-gtp-token': _session.gtp_token,
'xu-app-token': _session.app_token,
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw response.status;
}
const json = await response.json();
return json;
} catch (err) {
console.error(err);
if (err === 503) {
_this.func.UI.utils.progressScreen.show(SESSION_ID, `Error code ${err}, reloading in 5 sec`);
setTimeout(async () => {
await func.index.delete_pouch(SESSION_ID);
location.reload();
}, 5000);
}
return {};
}
};
try {
if (_session.engine_mode === 'live_preview') {
throw new Error('live_preview');
}
if (_session.engine_mode === 'miniapp') {
throw new Error('miniapp');
}
if (SESSION_OBJ?.[SESSION_ID]?.rpi_http_methods?.includes(serviceP)) {
const ret = await func.common.get_data_from_websocket(SESSION_ID, serviceP, data);
if (_data_system) {
// _data_system.SYS_GLOBAL_BOL_AJAX_BUSY = 0;
await set_ajax(0);
}
return ret;
} else {
throw new Error('method not found in rpi_http_methods');
}
} catch (err) {
const ret = await http();
if (_data_system) {
// _data_system.SYS_GLOBAL_BOL_AJAX_BUSY = 0;
await set_ajax(0);
}
return ret;
}
};
func.common.get_data_from_websocket = async function (SESSION_ID, serviceP, data) {
var _session = SESSION_OBJ[SESSION_ID];
return new Promise(function (resolve, reject) {
const dbs_calls = function () {
glb.websocket_queue_num++;
const obj = {
service: serviceP,
data,
websocket_queue_num: glb.websocket_queue_num,
};
if (glb.IS_WORKER) {
func.utils.post_back_to_client(SESSION_ID, 'get_dbs_data_from_websocket', _session.worker_id, obj);
self.addEventListener('get_ws_data_worker_' + glb.websocket_queue_num, (event) => {
resolve(event.detail.data);
});
// throw new Error("not ready yet");
} else {
if (RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED) {
RUNTIME_SERVER_WEBSOCKET.emit('message', obj);
const _ws_event = 'get_ws_data_response_' + glb.websocket_queue_num;
const _ws_handler = function (data) {
resolve(data.data);
func.runtime.platform.off('get_ws_data_response_' + data.e.websocket_queue_num, _ws_handler);
};
func.runtime.platform.on(_ws_event, _ws_handler);
} else {
throw new Error('fail to fetch from ws websocket inactive');
}
}
};
const heartbeat = function () {
const obj = {
service: 'heartbeat',
data,
};
if (RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED) {
RUNTIME_SERVER_WEBSOCKET.emit('message', obj);
const _hb_handler = function (data) {
resolve(data.data);
func.runtime.platform.off('heartbeat_response', _hb_handler);
};
func.runtime.platform.on('heartbeat_response', _hb_handler);
} else {
throw new Error('fail to fetch from ws websocket inactive');
}
};
if (serviceP === 'heartbeat') {
return heartbeat();
}
dbs_calls();
});
};
// func.common.sha256 = async function (inputString) {
// // 1. Create a hash buffer from the input string using SHA-256.
// // This part remains the same as it provides a strong, unique cryptographic starting point.
// const buffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(inputString));
// // 2. Interpret the first 8 bytes (64 bits) of the hash as one big number.
// const view = new DataView(buffer);
// const bigInt = view.getBigUint64(0, false); // `false` for big-endian
// // 3. Convert the BigInt to a Base36 string.
// // The .toString(36) method handles the conversion to an alphanumeric representation (0-9, a-z).
// const base36Hash = bigInt.toString(36);
// // 4. Take the first 10 characters. If it's shorter, it will just return the whole string.
// // For a 64-bit integer, the Base36 representation will be about 13 characters long,
// // so slicing is a reliable way to get a fixed length.
// const shortHash = base36Hash.slice(0, 10);
// // 5. Pad the start in the unlikely case the hash is shorter than 10 characters.
// // This ensures the output is always exactly 10 characters long.
// return shortHash.padStart(10, '0');
// };
func.common.fastHash = function (inputString) {
let hash = 0x811c9dc5; // FNV offset basis
for (let i = 0; i < inputString.length; i++) {
hash ^= inputString.charCodeAt(i);
// FNV prime multiplication with 32-bit overflow
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
// Convert to base36 and pad to 10 characters
return ((hash >>> 0).toString(36) + '0000000000').slice(0, 10);
};
glb.new_xu_render = false;
// XU_PERF: opt-in fast paths (shallow render-context copies, drive-ref
// pre-scan, keyed xu-for reuse). Default off = legacy behavior byte-for-byte.
glb.XU_PERF = glb.XU_PERF || false;
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Shared render-tree contract helpers live here so browser and headless runtimes can resolve the same UI structure.
func.runtime.render.TREE_CONTRACT_VERSION = func.runtime.render.TREE_CONTRACT_VERSION || 'xuda.render_tree.v1';
func.runtime.render._tree_widget_capability_cache = func.runtime.render._tree_widget_capability_cache || {};
func.runtime.render.safe_clone_tree_value = function (value) {
if (typeof structuredClone === 'function') {
try {
return structuredClone(value);
} catch (_) {
// Fall through to the recursive clone below.
}
}
if (Array.isArray(value)) {
return value.map(function (item) {
return func.runtime.render.safe_clone_tree_value(item);
});
}
if (value && typeof value === 'object') {
const cloned = {};
const keys = Object.keys(value);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
cloned[key] = func.runtime.render.safe_clone_tree_value(value[key]);
}
return cloned;
}
return value;
};
func.runtime.render.sort_tree_debug_value = function (value) {
if (Array.isArray(value)) {
return value.map(function (item) {
return func.runtime.render.sort_tree_debug_value(item);
});
}
if (value && typeof value === 'object') {
const sorted = {};
const keys = Object.keys(value).sort();
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
sorted[key] = func.runtime.render.sort_tree_debug_value(value[key]);
}
return sorted;
}
return value;
};
func.runtime.render.is_tree_node = function (nodeP) {
return !!nodeP?.contract && nodeP.contract === func.runtime.render.TREE_CONTRACT_VERSION;
};
func.runtime.render.get_tree_source_node = function (nodeP) {
if (!func.runtime.render.is_tree_node(nodeP)) {
return nodeP || null;
}
return nodeP?.meta?.source_node || null;
};
func.runtime.render.get_tree_source_snapshot = function (nodeP) {
if (!func.runtime.render.is_tree_node(nodeP)) {
return func.runtime.render.safe_clone_tree_value(nodeP);
}
return nodeP?.meta?.source_snapshot || null;
};
func.runtime.render.get_tree_node_kind = function (nodeP) {
const tag_name = typeof nodeP?.tagName === 'string' ? nodeP.tagName.toLowerCase() : '';
const node_type = typeof nodeP?.type === 'string' ? nodeP.type.toLowerCase() : '';
if (tag_name === 'xu-widget') return 'widget';
if (tag_name === 'xu-single-view') return 'single_view';
if (tag_name === 'xu-multi-view') return 'multi_view';
if (tag_name === 'xu-panel') return 'panel';
if (tag_name === 'xu-teleport') return 'teleport';
if (tag_name === 'xurender') return 'placeholder';
if (tag_name === '#text' || node_type === 'text') return 'text';
if (!tag_name && typeof nodeP?.content === 'string' && !Array.isArray(nodeP?.children)) return 'text';
return 'element';
};
func.runtime.render.get_tree_node_id = function (nodeP, pathP) {
if (nodeP?.id) {
return nodeP.id;
}
if (nodeP?.id_org) {
return nodeP.id_org;
}
const normalized_path = Array.isArray(pathP) && pathP.length ? pathP.join('.') : 'root';
return `tree-node-${normalized_path}`;
};
func.runtime.render.get_tree_controls = function (attributes) {
const attrs = attributes || {};
const get_first_defined = function (keys) {
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
if (Object.prototype.hasOwnProperty.call(attrs, key)) {
return attrs[key];
}
}
return null;
};
return {
xu_for: get_first_defined(['xu-for', 'xu-exp:xu-for']),
xu_if: get_first_defined(['xu-if', 'xu-exp:xu-if']),
xu_render: get_first_defined(['xu-render', 'xu-exp:xu-render']),
};
};
func.runtime.render.get_tree_node_capabilities = async function (options) {
const attributes = options?.attributes || {};
const plugin_name = attributes['xu-widget'];
if (!plugin_name) {
return null;
}
const cache = func.runtime.render._tree_widget_capability_cache;
if (cache[plugin_name]) {
return func.runtime.render.safe_clone_tree_value(cache[plugin_name]);
}
let capabilities = {
browser: true,
headless: false,
};
try {
if (options.SESSION_ID && options.paramsP && func.runtime.widgets?.create_context && func.runtime.widgets?.get_definition) {
const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, attributes);
const definition = await func.runtime.widgets.get_definition(widget_context);
capabilities = func.runtime.widgets.normalize_capabilities(definition);
}
} catch (_) {
// Keep the safe browser-only default when the widget definition is unavailable.
}
cache[plugin_name] = capabilities;
return func.runtime.render.safe_clone_tree_value(capabilities);
};
func.runtime.render.ensure_tree_node = async function (options) {
if (!options?.nodeP) {
return null;
}
if (func.runtime.render.is_tree_node(options.nodeP)) {
return options.nodeP;
}
return await func.runtime.render.build_tree(options);
};
func.runtime.render.build_tree = async function (options) {
if (Array.isArray(options?.nodeP)) {
return await func.runtime.render.build_tree_list({
...options,
nodesP: options.nodeP,
});
}
const nodeP = options?.nodeP;
if (!nodeP) {
return null;
}
if (func.runtime.render.is_tree_node(nodeP)) {
return nodeP;
}
const pathP = Array.isArray(options?.pathP) ? options.pathP.slice() : [];
const tree_path = pathP.length ? pathP.slice() : [0];
const attributes = func.runtime.render.safe_clone_tree_value(nodeP.attributes || {});
const has_child_nodes = Array.isArray(nodeP.children) && nodeP.children.length > 0;
if (typeof nodeP.content !== 'undefined' && typeof attributes['xu-content'] === 'undefined' && !has_child_nodes && nodeP.content !== '') {
attributes['xu-content'] = func.runtime.render.safe_clone_tree_value(nodeP.content);
}
const widget_capabilities = await func.runtime.render.get_tree_node_capabilities({
SESSION_ID: options?.SESSION_ID,
paramsP: options?.paramsP,
attributes,
});
const children = [];
const child_nodes = Array.isArray(nodeP.children) ? nodeP.children : [];
const parent_tree_id = tree_path.join('.');
for (let index = 0; index < child_nodes.length; index++) {
const child_tree = await func.runtime.render.build_tree({
...options,
nodeP: child_nodes[index],
pathP: tree_path.concat(index),
parent_tree_id: parent_tree_id,
keyP: index,
parent_nodeP: nodeP,
});
if (child_tree) {
children.push(child_tree);
}
}
const tree = {
contract: func.runtime.render.TREE_CONTRACT_VERSION,
id: func.runtime.render.get_tree_node_id(nodeP, tree_path),
xu_tree_id: `tree.${tree_path.join('.')}`,
kind: func.runtime.render.get_tree_node_kind(nodeP),
tagName: nodeP.tagName || null,
attributes,
text: typeof nodeP.text !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.text) : null,
content: typeof nodeP.content !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.content) : null,
children,
meta: {
tree_id: tree_path.join('.'),
path: tree_path,
parent_tree_id: options?.parent_tree_id || null,
key: typeof options?.keyP === 'undefined' ? null : options.keyP,
recordid: nodeP?.recordid || null,
dependency_fields: func.runtime.render.safe_clone_tree_value(nodeP?.dependency_fields || null),
iterate_info: func.runtime.render.safe_clone_tree_value(options?.parent_infoP?.iterate_info || nodeP?.iterate_info || null),
controls: func.runtime.render.get_tree_controls(attributes),
capabilities: widget_capabilities,
widget: attributes['xu-widget']
? {
plugin_name: attributes['xu-widget'],
method: attributes['xu-method'] || '_default',
capabilities: widget_capabilities,
}
: null,
source_node_id: nodeP?.id || nodeP?.id_org || null,
source_node: nodeP,
source_snapshot: func.runtime.ui?.get_node_snapshot
? func.runtime.ui.get_node_snapshot(nodeP)
: func.runtime.render.safe_clone_tree_value(nodeP),
},
};
return tree;
};
func.runtime.render.build_tree_list = async function (options) {
const nodes = Array.isArray(options?.nodesP) ? options.nodesP : [];
const trees = [];
for (let index = 0; index < nodes.length; index++) {
const tree = await func.runtime.render.build_tree({
...options,
nodeP: nodes[index],
pathP: Array.isArray(options?.pathP) && options.pathP.length ? options.pathP.concat(index) : [index],
keyP: index,
});
if (tree) {
trees.push(tree);
}
}
return trees;
};
func.runtime.render.sanitize_tree_for_debug = function (treeP) {
if (Array.isArray(treeP)) {
return treeP.map(function (child) {
return func.runtime.render.sanitize_tree_for_debug(child);
});
}
if (!func.runtime.render.is_tree_node(treeP)) {
return func.runtime.render.sort_tree_debug_value(func.runtime.render.safe_clone_tree_value(treeP));
}
return {
contract: treeP.contract,
id: treeP.id,
xu_tree_id: treeP.xu_tree_id || null,
kind: treeP.kind,
tagName: treeP.tagName,
attributes: func.runtime.render.sort_tree_debug_value(treeP.attributes || {}),
text: treeP.text,
content: treeP.content,
children: treeP.children.map(function (child) {
return func.runtime.render.sanitize_tree_for_debug(child);
}),
meta: {
tree_id: treeP.meta?.tree_id || null,
path: func.runtime.render.safe_clone_tree_value(treeP.meta?.path || []),
parent_tree_id: treeP.meta?.parent_tree_id || null,
key: typeof treeP.meta?.key === 'undefined' ? null : treeP.meta.key,
recordid: treeP.meta?.recordid || null,
dependency_fields: func.runtime.render.sort_tree_debug_value(treeP.meta?.dependency_fields || null),
iterate_info: func.runtime.render.sort_tree_debug_value(treeP.meta?.iterate_info || null),
controls: func.runtime.render.sort_tree_debug_value(treeP.meta?.controls || null),
capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta?.capabilities || null),
widget: treeP.meta?.widget
? {
plugin_name: treeP.meta.widget.plugin_name,
method: treeP.meta.widget.method,
capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta.widget.capabilities || null),
}
: null,
source_node_id: treeP.meta?.source_node_id || null,
},
};
};
func.runtime.render.serialize_tree = function (treeP, spacing = 2) {
return JSON.stringify(func.runtime.render.sanitize_tree_for_debug(treeP), null, spacing);
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Shared string-renderer helpers live here so headless/server runtimes can materialize the render tree without a DOM.
func.runtime.render.HTML_VOID_TAGS = func.runtime.render.HTML_VOID_TAGS || {
area: true,
base: true,
br: true,
col: true,
embed: true,
hr: true,
img: true,
input: true,
link: true,
meta: true,
param: true,
source: true,
track: true,
wbr: true,
};
func.runtime.render.escape_html = function (value) {
return `${value ?? ''}`
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
};
func.runtime.render.escape_html_attribute = function (value) {
return func.runtime.render.escape_html(value);
};
func.runtime.render.is_html_void_tag = function (tag_name) {
return !!func.runtime.render.HTML_VOID_TAGS[(tag_name || '').toLowerCase()];
};
func.runtime.render.is_falsey_render_value = function (value) {
if (value === false || value === null || typeof value === 'undefined') {
return true;
}
if (typeof value === 'number') {
return value === 0;
}
if (typeof value === 'string') {
const normalized = value.trim().toLowerCase();
return normalized === '' || normalized === 'false' || normalized === '0' || normalized === 'null' || normalized === 'undefined' || normalized === 'off' || normalized === 'no';
}
return false;
};
func.runtime.render.should_render_tree_node = function (treeP) {
const controls = treeP?.meta?.controls || {};
if (controls.xu_if !== null && controls.xu_if !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_if)) {
return false;
}
if (controls.xu_render !== null && controls.xu_render !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_render)) {
return false;
}
return true;
};
func.runtime.render.is_tree_control_attribute = function (key) {
if (!key) {
return false;
}
return (
key.startsWith('xu-exp:') ||
key === 'xu-widget' ||
key === 'xu-method' ||
key === 'xu-for' ||
key === 'xu-for-key' ||
key === 'xu-for-val' ||
key === 'xu-if' ||
key === 'xu-render' ||
key === 'xu-bind' ||
key === 'xu-content' ||
key === 'xu-text' ||
key === 'xu-html' ||
key === 'xu-show' ||
key === 'xu-panel-program' ||
key === 'xu-teleport'
);
};
func.runtime.render.get_string_renderer_tag_name = function (treeP) {
switch (treeP?.kind) {
case 'widget':
case 'single_view':
case 'multi_view':
case 'panel':
case 'teleport':
return 'div';
case 'placeholder':
return null;
case 'text':
return null;
default:
return treeP?.tagName || 'div';
}
};
func.runtime.render.get_tree_terminal_content = function (treeP) {
const attributes = treeP?.attributes || {};
if (typeof attributes['xu-html'] !== 'undefined' && attributes['xu-html'] !== null) {
return {
value: `${attributes['xu-html']}`,
mode: 'html',
};
}
if (typeof attributes['xu-content'] !== 'undefined' && attributes['xu-content'] !== null) {
return {
value: `${attributes['xu-content']}`,
mode: 'html',
};
}
if (typeof attributes['xu-text'] !== 'undefined' && attributes['xu-text'] !== null) {
return {
value: `${attributes['xu-text']}`,
mode: 'text',
};
}
if (treeP?.kind === 'text') {
return {
value: typeof treeP?.text !== 'undefined' && treeP?.text !== null ? `${treeP.text}` : `${treeP?.content || ''}`,
mode: 'text',
};
}
return null;
};
func.runtime.render.render_tree_terminal_content = function (treeP) {
const terminal = func.runtime.render.get_tree_terminal_content(treeP);
if (!terminal) {
return null;
}
if (terminal.mode === 'html') {
return terminal.value;
}
return func.runtime.render.escape_html(terminal.value);
};
func.runtime.render.get_widget_fallback_markup = function (treeP) {
const widget_meta = treeP?.meta?.widget || {};
const capability_state = widget_meta?.capabilities?.headless ? 'headless-capable' : 'browser-only';
return `<!--xuda-widget:${func.runtime.render.escape_html(widget_meta.plugin_name || 'unknown')}:${capability_state}-->`;
};
func.runtime.render.get_tree_string_attributes = function (treeP, renderer_context) {
const attributes = func.runtime.render.safe_clone_tree_value(treeP?.attributes || {});
const attr_pairs = [];
const keys = Object.keys(attributes);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
if (func.runtime.render.is_tree_control_attribute(key)) {
continue;
}
const value = attributes[key];
if (value === false || value === null || typeof value === 'undefined') {
continue;
}
if (value === true) {
attr_pairs.push(key);
continue;
}
const normalized_value = typeof value === 'object' ? JSON.stringify(value) : `${value}`;
attr_pairs.push(`${key}="${func.runtime.render.escape_html_attribute(normalized_value)}"`);
}
attr_pairs.push(`data-xuda-kind="${func.runtime.render.escape_html_attribute(treeP?.kind || 'element')}"`);
attr_pairs.push(`data-xuda-node-id="${func.runtime.render.escape_html_attribute(treeP?.id || treeP?.meta?.source_node_id || '')}"`);
attr_pairs.push(`data-xuda-tree-id="${func.runtime.render.escape_html_attribute(treeP?.meta?.tree_id || '')}"`);
if (treeP?.kind === 'widget' && treeP?.meta?.widget) {
attr_pairs.push(`data-xuda-widget="${func.runtime.render.escape_html_attribute(treeP.meta.widget.plugin_name || '')}"`);
attr_pairs.push(`data-xuda-widget-method="${func.runtime.render.escape_html_attribute(treeP.meta.widget.method || '_default')}"`);
attr_pairs.push(`data-xuda-widget-capability="${func.runtime.render.escape_html_attribute(treeP.meta.widget.capabilities?.headless ? 'headless' : 'browser')}"`);
}
if (treeP?.kind === 'teleport' && treeP?.attributes?.['xu-teleport']) {
attr_pairs.push(`data-xuda-teleport-target="${func.runtime.render.escape_html_attribute(treeP.attributes['xu-teleport'])}"`);
}
if ((treeP?.meta?.controls?.xu_for !== null && treeP?.meta?.controls?.xu_for !== undefined) && !renderer_context?.strip_iteration_markers) {
attr_pairs.push('data-xuda-xu-for="pending"');
}
return attr_pairs.length ? ' ' + attr_pairs.join(' ') : '';
};
func.runtime.render.render_tree_children_to_string = async function (treeP, renderer_context) {
if (!Array.isArray(treeP?.children) || !treeP.children.length) {
return '';
}
let html = '';
for (let index = 0; index < treeP.children.length; index++) {
html += await func.runtime.render.render_tree_to_string(treeP.children[index], {
...renderer_context,
parent_tree: treeP,
});
}
return html;
};
func.runtime.render.render_tree_to_string = async function (treeP, renderer_context = {}) {
if (!treeP) {
return '';
}
if (Array.isArray(treeP)) {
let html = '';
for (let index = 0; index < treeP.length; index++) {
html += await func.runtime.render.render_tree_to_string(treeP[index], renderer_context);
}
return html;
}
const ensured_tree = await func.runtime.render.ensure_tree_node({
SESSION_ID: renderer_context?.SESSION_ID,
nodeP: treeP,
paramsP: renderer_context?.paramsP,
parent_infoP: renderer_context?.parent_infoP,
keyP: renderer_context?.keyP,
parent_nodeP: renderer_context?.parent_nodeP,
});
if (!ensured_tree || !func.runtime.render.should_render_tree_node(ensured_tree)) {
return '';
}
if (ensured_tree.kind === 'placeholder') {
if (renderer_context?.include_placeholders) {
return `<!--xuda-placeholder:${func.runtime.render.escape_html(ensured_tree.id || '')}-->`;
}
return '';
}
if (ensured_tree.kind === 'text') {
return func.runtime.render.render_tree_terminal_content(ensured_tree) || '';
}
const tag_name = func.runtime.render.get_string_renderer_tag_name(ensured_tree);
if (!tag_name || tag_name.toLowerCase() === 'script') {
return '';
}
const attributes = func.runtime.render.get_tree_string_attributes(ensured_tree, renderer_context);
const terminal_content = func.runtime.render.render_tree_terminal_content(ensured_tree);
let children_html = terminal_content !== null ? terminal_content : await func.runtime.render.render_tree_children_to_string(ensured_tree, renderer_context);
if (ensured_tree.kind === 'widget' && !children_html) {
children_html = func.runtime.render.get_widget_fallback_markup(ensured_tree);
}
if (func.runtime.render.is_html_void_tag(tag_name)) {
return `<${tag_name}${attributes}>`;
}
return `<${tag_name}${attributes}>${children_html}</${tag_name}>`;
};
func.runtime.render.render_to_string = async function (options = {}) {
const treeP = await func.runtime.render.ensure_tree_node({
SESSION_ID: options.SESSION_ID,
nodeP: options.treeP || options.nodeP,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
});
return await func.runtime.render.render_tree_to_string(treeP, options);
};
func.runtime.render.get_server_render_mode = function (options = {}) {
const normalized = func.runtime.render.normalize_runtime_bootstrap({
app_computing_mode: options.app_computing_mode,
app_render_mode: options.app_render_mode,
app_client_activation: options.app_client_activation,
});
return normalized;
};
func.runtime.render.build_server_render_params = async function (options = {}) {
const SESSION_ID = options.SESSION_ID;
const prog_id = options.prog_id;
const dsSessionP = options.dsSessionP;
const _session = SESSION_OBJ?.[SESSION_ID] || {};
const _ds = _session?.DS_GLB?.[dsSessionP] || {};
const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
if (!viewDoc?.properties) {
throw new Error(`view document not found for ${prog_id}`);
}
const base_params = _ds?.screen_params ? func.runtime.render.safe_clone_tree_value(_ds.screen_params) : {};
const screenId = options.screenId || base_params.screenId || `ssr_${prog_id}_${dsSessionP || '0'}`;
const paramsP = {
...base_params,
prog_id,
sourceScreenP: null,
$callingContainerP: null,
triggerIdP: null,
callingDataSource_objP: _ds,
rowIdP: typeof options.rowIdP !== 'undefined' ? options.rowIdP : (_ds?.currentRecordId || null),
renderType: viewDoc.properties?.renderType,
parameters_obj_inP: options.parameters_obj_inP || base_params.parameters_obj_inP || options.parameters_raw_obj || {},
source_functionP: options.source_functionP || base_params.source_functionP || 'render_string',
is_panelP: false,
screen_type: options.screen_type || base_params.screen_type || 'render_string',
screenInfo: viewDoc,
call_screen_propertiesP: base_params.call_screen_propertiesP,
parentDataSourceNoP: typeof _ds?.parentDataSourceNo === 'undefined' || _ds?.parentDataSourceNo === null ? 0 : _ds.parentDataSourceNo,
parameters_raw_obj: options.parameters_raw_obj || base_params.parameters_raw_obj || {},
dsSessionP,
screenId,
containerIdP: base_params.containerIdP || `ssr_container_${screenId}`,
};
if (_ds) {
_ds.screen_params = paramsP;
}
return paramsP;
};
func.runtime.render.build_prog_tree = async function (options = {}) {
const SESSION_ID = options.SESSION_ID;
const prog_id = options.prog_id;
const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
if (!viewDoc?.progUi?.length) {
throw new Error(`progUi not found for ${prog_id}`);
}
const paramsP = options.paramsP || (await func.runtime.render.build_server_render_params({
...options,
SESSION_ID,
prog_id,
viewDoc,
}));
const root_index = typeof options.root_index === 'number' ? options.root_index : 0;
const root_node = func.runtime.render.safe_clone_tree_value(viewDoc.progUi[root_index]);
const tree = await func.runtime.render.build_tree({
SESSION_ID,
nodeP: root_node,
paramsP,
});
return {
tree,
paramsP,
viewDoc,
};
};
func.runtime.render.build_ssr_payload = function (render_program, options = {}) {
const runtime_profile = func.runtime.render.get_server_render_mode(options);
return {
contract: 'xuda.ssr.v1',
prog_id: options.prog_id,
screenId: render_program.paramsP.screenId,
containerId: render_program.paramsP.containerIdP,
app_computing_mode: runtime_profile.app_computing_mode,
app_render_mode: runtime_profile.app_render_mode,
app_client_activation: runtime_profile.app_client_activation,
tree_contract: func.runtime.render.TREE_CONTRACT_VERSION,
};
};
func.runtime.render.build_ssr_screen_html = function (html, render_program, options = {}) {
const payload = func.runtime.render.build_ssr_payload(render_program, options);
const screenId = func.runtime.render.escape_html_attribute(payload.screenId || '');
const containerId = func.runtime.render.escape_html_attribute(payload.containerId || '');
const activation = func.runtime.render.escape_html_attribute(payload.app_client_activation || 'takeover');
return `<div data-xuda-ssr-embed="true" class="xu_embed_div"><div id="${screenId}" class="xu_embed_container" data-xuda-ssr-screen="true" data-xuda-ssr-screen-id="${screenId}" data-xuda-activation="${activation}" style="display: contents;"><div id="${containerId}" data-xuda-ssr-root-frame="true" data-xuda-ssr-screen-id="${screenId}" data-xuda-activation="${activation}" style="display: contents;">${html}</div></div></div>`;
};
func.runtime.render.render_prog_to_string = async function (options = {}) {
const render_program = await func.runtime.render.build_prog_tree(options);
const html = await func.runtime.render.render_to_string({
...options,
SESSION_ID: options.SESSION_ID,
treeP: render_program.tree,
paramsP: render_program.paramsP,
});
const ssr_payload = func.runtime.render.build_ssr_payload(render_program, options);
const screen_html = func.runtime.render.build_ssr_screen_html(html, render_program, options);
return {
prog_id: options.prog_id,
dsSessionP: render_program.paramsP.dsSessionP,
screenId: render_program.paramsP.screenId,
html,
screen_html,
tree_json: func.runtime.render.serialize_tree(render_program.tree),
paramsP: render_program.paramsP,
ssr_payload,
};
};
func.runtime = func.runtime || {};
func.runtime.platform = func.runtime.platform || {};
func.runtime.platform.dispatch_document_event = function (name, data) {
if (typeof document === 'undefined' || typeof CustomEvent === 'undefined') {
return false;
}
document.dispatchEvent(new CustomEvent(name, {
detail: Array.isArray(data) ? data : [data],
}));
return true;
};
glb.DEBUG_INFO_OBJ = {};
// var CONNECTION_ATTEMPTS = 0;
glb.APP_INFO = {};
// var vars = {};
// var USERS_CACHE = {};
var SYSTEM_READY = null;
// var CPI_WEBSOCKET;
var GLB_JS_SCRIPTS_LOADED = [];
var STUDIO_WEBSOCKET = null;
var STUDIO_WEBSOCKET_CONNECTION_ID = null;
var STUDIO_PEER = null;
var STUDIO_PEER_CONN_SEND_METHOD = null;
var STUDIO_PEER_CONN_ID = null;
var SUPPORT_PEER = null;
var SUPPORT_PEER_CONN = null;
var STUDIO_PEER_CONN_MSG_QUEUE = [];
var CLIENT_ACTIVITY_TS;
var IS_ONLINE;
glb.REFERENCE_LESS_FUNCTIONS = ['update', 'raise_event', 'call_library', 'invoke_action', 'loader_on', 'loader_off', 'emit_event', 'delay', 'execute_evaluate_javascript', 'execute_native_javascript'];
var CACHE_PROG_UI = {};
var ALERT_IS_ACTIVE = false;
glb.WORKER_ATTEMPTS_NOT_RESPONDING = 200000;
glb.WORKER_TIMEOUT = 600000;
glb.WORKER_PAUSE = false;
// glb.EMAIL_SEND_RECEIVE_STATUS = {};
var DATASOURCE_INTERVALS = {};
var APP_MODAL_OBJ = {};
var CURRENT_APP_POPOVER = null;
var ELEMENT_CLICK_EVENT = null;
var posX = 0; //cursor x
var posY = 0; //cursor x
var LOADER_ACTIVE = false;
var LOADER_TEXT = '';
var REFRESHER_IN_PROGRESS = false;
glb.screen_num = 0;
var RESPONSE_FROM_STUDIO_QUEUE = {};
var SCREEN_BLOCKER_OBJ = {};
var IS_PROGRESS_SCREEN_OPEN = null;
// var UI_ENGINE_OBJ = null;
var UI_WORKER_OBJ = {
jobs: [],
num: 9000,
cache: {},
viewport_height_set_ids: [],
xu_render_cache: {},
// pending_for_viewport_render: {}
};
glb.html5_events_handler = [
'onabort',
'onafterprint',
'onautocomplete',
'onautocompleteerror',
'onbeforeprint',
'onbeforeunload',
'onblur',
'oncancel',
'oncanplay',
'oncanplaythrough',
'onchange',
'onclick',
'onclose',
'oncontextmenu',
'oncopy',
'oncuechange',
'oncut',
'ondblclick',
'ondrag',
'ondragend',
'ondragenter',
'ondragexit',
'ondragleave',
'ondragover',
'ondragstart',
'ondrop',
'ondurationchange',
'onemptied',
'onended',
'onerror',
'onfocus',
'onhashchange',
'oninput',
'oninvalid',
'onkeydown',
'onkeypress',
'onkeyup',
'onload',
'onloadeddata',
'onloadedmetadata',
'onloadstart',
'onmessage',
'onmousedown',
'onmouseenter',
'onmouseleave',
'onmousemove',
'onmouseout',
'onmouseover',
'onmouseup',
'onmousewheel',
'onoffline',
'ononline',
'onpagehide',
'onpageshow',
'onpaste',
'onpause',
'onplay',
'onplaying',
'onpopstate',
'onprogress',
'onratechange',
'onreset',
'onresize',
'onscroll',
'onsearch',
'onseeked',
'onseeking',
'onselect',
'onshow',
'onsort',
'onstalled',
'onstorage',
'onsubmit',
'onsuspend',
'ontimeupdate',
'ontoggle',
'onunload',
'onvolumechange',
'onwaiting',
];
glb.lifecycle = {
plugins: {},
// queue: [],
fn_arr: ['beforeInit', 'initialized', 'systemReady', 'beforeMounted', 'mounted'],
// add(type, fn, params) {
// this.queue.push({ type, fn, params });
// },
execute: async function (SESSION_ID, event) {
const _session = SESSION_OBJ[SESSION_ID];
const xu_api = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
var params = {
SESSION_ID,
session_data: _session,
app_obj: APP_OBJ[_session.app_id],
xu_api,
};
for await (const [plugin_name, val] of Object.entries(glb.lifecycle.plugins)) {
if (val?.plugin_script?.[event]) {
params.setup_data = val.setup_data;
await val.plugin_script[event](params);
}
}
},
};
glb.run_xu_before = [
'xu-cdn',
'xu-style',
'xu-render',
'xu-for-key',
'xu-for-val',
// "xu-ui-plugin",
// "programParameters",
];
glb.run_xu_after = ['xu-bind', 'xu-class', 'xu-script', 'xu-ui-plugin', 'xu-ref'];
glb.attr_abbreviations_arr = ['xu-click', 'xu-dblclick', 'xu-contextmenu', 'xu-focus', 'xu-keyup', 'xu-change', 'xu-blur', 'xu-init'];
glb.solid_attributes = ['disabled'];
func.datasource = {};
// Preserve imperatively-set virtual fields without declarative expressions across the datasource refresh
// row-rebuild, so a refresh does not reset them to static defaults. Kept in a WeakMap off _ds so it never
// enters the update_xu_ref snapshot.
func.datasource.__vf_preserve = new WeakMap();
func.datasource._debug_summarize_set_data_feed = function (data_feed) {
const rows = data_feed?.rows;
const first_row = Array.isArray(rows) ? rows[0] : null;
const summarized_row = {};
if (first_row && typeof first_row === 'object') {
for (const key of Object.keys(first_row).slice(0, 12)) {
const value = first_row[key];
if (typeof value === 'string') {
summarized_row[key] = {
type: 'string',
length: value.length,
empty: value.length === 0,
};
} else if (Array.isArray(value)) {
summarized_row[key] = {
type: 'array',
length: value.length,
};
} else {
summarized_row[key] = {
type: typeof value,
value: value && typeof value === 'object' ? '[object]' : value,
};
}
}
}
return {
rows_length: Array.isArray(rows) ? rows.length : null,
rows_changed_length: Array.isArray(data_feed?.rows_changed) ? data_feed.rows_changed.length : null,
rows_added_length: Array.isArray(data_feed?.rows_added) ? data_feed.rows_added.length : null,
rows_deleted_length: Array.isArray(data_feed?.rows_deleted) ? data_feed.rows_deleted.length : null,
first_row: summarized_row,
};
};
func.datasource.create = async function (
SESSION_ID,
prog_id,
dataSourceNoP = null, // if exist then refresh
parentDataSourceNoP,
containerIdP,
rowIdP,
jobNoP,
calling_trigger_prop,
parameters_raw_obj,
NA_isInitP,
NA_callingSourceP,
calling_jobP,
NA_screen_dsP,
is_panelP,
parameters_obj_inP,
static_refreshP,
worker_id,
NA_eventChangesResults,
) {
// Coalesce concurrent refreshes of the same existing datasource onto the first in-flight create
// promise. Without this, a refresh-triggered render can synchronously re-enter create() for the
// same datasource while the previous pass is still rebuilding rows. Brand-new datasource creation
// is not coalesced, and the flag clears on settle so the next real refresh runs normally.
let _guard_ds = null;
if (dataSourceNoP != null) {
const _guard_session = SESSION_OBJ[SESSION_ID];
_guard_ds = _guard_session?.DS_GLB?.[dataSourceNoP];
if (_guard_ds && _guard_ds._create_in_flight) {
return _guard_ds._create_in_flight;
}
}
const _create_promise = new Promise(async function (resolve, reject) {
if (!prog_id) return reject('Program is empty');
var _session = SESSION_OBJ[SESSION_ID];
if (!_session.DS_GLB) return reject('DS_GLB not exist');
var _prog_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, prog_id);
if (!_prog_obj) return reject('Program not found');
var args = {
SESSION_ID,
prog_id,
dataSourceNoP,
parentDataSourceNoP,
containerIdP,
rowIdP,
jobNoP,
calling_trigger_prop,
calling_jobP,
is_panelP,
parameters_obj_inP,
static_refreshP,
worker_id,
parameters_raw_obj,
};
var IS_DATASOURCE_REFRESH = null;
var _ds = _session.DS_GLB[dataSourceNoP];
var old_dataSource_vars = {};
if (_ds) IS_DATASOURCE_REFRESH = true;
if (IS_DATASOURCE_REFRESH) {
old_dataSource_vars.sortOrder = _ds.sortOrder;
old_dataSource_vars.sortOrderTypeExp = _ds.sortOrderTypeExp;
if (_ds.data_system) {
old_dataSource_vars.SYS_OBJ_WIN_MODE = _ds.data_system.SYS_OBJ_WIN_MODE;
// old_dataSource_vars.SYS_OBJ_WIN_UI = _ds.data_system.SYS_OBJ_WIN_UI;
old_dataSource_vars.SYS_STR_WIN_ID = _ds.data_system.SYS_STR_WIN_ID;
old_dataSource_vars.SYS_STR_WIN_NAME = _ds.data_system.SYS_STR_WIN_NAME;
}
if (static_refreshP) old_dataSource_vars.in_parameters = _ds.in_parameters;
await func.datasource.update(SESSION_ID, {
[dataSourceNoP]: {
['datasource_main']: {
stat: 'busy',
stat_ts: Date.now(),
is_worker: glb.IS_WORKER,
},
},
});
}
const restore_old_dataSource_vars = function (dsSessionP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (_ds.data_system) {
_ds.data_system.SYS_OBJ_WIN_MODE = old_dataSource_vars.SYS_OBJ_WIN_MODE;
// _ds.data_system.SYS_OBJ_WIN_UI = old_dataSource_vars.SYS_OBJ_WIN_UI;
_ds.data_system.SYS_STR_WIN_ID = old_dataSource_vars.SYS_STR_WIN_ID;
_ds.data_system.SYS_STR_WIN_NAME = old_dataSource_vars.SYS_STR_WIN_NAME;
}
if (static_refreshP) _ds.in_parameters = old_dataSource_vars.in_parameters;
};
var run_at = _prog_obj?.properties?.runAt;
if (_session.opt.app_computing_mode === 'main') {
run_at = 'client';
}
if (_prog_obj?.properties.menuType === 'globals') {
run_at = 'client';
}
if (!run_at && parentDataSourceNoP && _session.DS_GLB[parentDataSourceNoP]) {
if (_session.DS_GLB[parentDataSourceNoP]._run_at) run_at = _session.DS_GLB[parentDataSourceNoP].v.run_at;
}
const done = function (SESSION_ID, dsSessionP, response_returned_from_worker) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (IS_DATASOURCE_REFRESH) {
restore_old_dataSource_vars(dsSessionP);
}
if (!IS_DATASOURCE_REFRESH) {
if (!glb.IS_WORKER) {
DATASOURCE_INTERVALS[SESSION_ID][dsSessionP] = new func.datasource.interval(SESSION_ID, dsSessionP, 'client_interval');
DATASOURCE_INTERVALS[SESSION_ID][dsSessionP].init();
}
}
const set_stat_idle = async function () {
let ds_connected = [];
for (const [dsP, _ds] of Object.entries(_session.DS_GLB)) {
if (_ds.parentDataSourceNo == dsSessionP) {
ds_connected.push(dsP);
}
}
const datasource_changes = {
[dsSessionP]: {
['datasource_main']: {
stat: 'idle',
stat_ts: Date.now(),
is_worker: glb.IS_WORKER,
},
},
};
if (!ds_connected.length) {
return await func.datasource.update(SESSION_ID, datasource_changes);
}
let interval = setInterval(() => {
let idle_count = 0;
for (const dsSession of ds_connected) {
const _ds = _session.DS_GLB[dsSession];
// A connected child datasource that is missing/removed from DS_GLB can never become
// "idle"; reading _ds.stat would throw every tick, so the interval would never clear
// and this datasource would stay non-idle. Treat a missing child as idle so the wait can
// complete.
if (!_ds || _ds.stat == 'idle') {
idle_count++;
}
}
if (ds_connected.length === idle_count) {
clearInterval(interval);
func.datasource.update(SESSION_ID, datasource_changes);
}
}, 1000);
};
set_stat_idle();
resolve({
SESSION_ID,
dsSessionP,
rowIdP: _ds.args.rowIdP,
jobNoP: _ds.args.jobNoP,
callingLogId: _ds.callingLogId,
calling_jobP: _ds.calling_jobP,
});
};
var db_driver;
var is_system_client_vars = false;
if (jobNoP) {
}
if (glb.IS_WORKER || run_at === 'client' || is_system_client_vars || db_driver === 'pouchdb') {
const ret = await func.datasource.prepare(
args.SESSION_ID,
args.prog_id,
args.dataSourceNoP,
args.parentDataSourceNoP,
args.containerIdP,
args.rowIdP,
args.jobNoP,
args.calling_trigger_prop,
args.parameters_raw_obj,
null,
null,
args.calling_jobP,
null,
args.is_panelP,
args.parameters_obj_inP,
args.static_refreshP,
run_at,
worker_id,
);
return done(SESSION_ID, ret.dsSessionP);
}
// vvvv run at worker vvvv
if (_ds) IS_DATASOURCE_REFRESH = true;
var data = Object.assign(
{
session_id: SESSION_ID,
dataSourceSessionGlobal: SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal,
parentDataSourceNo: IS_DATASOURCE_REFRESH ? _ds.parentDataSourceNo : null,
IS_DATASOURCE_REFRESH,
},
args,
);
delete data.SESSION_ID;
const jsonP = await func.index.call_worker(SESSION_ID, {
service: 'datasource_create',
data,
id: SESSION_OBJ[SESSION_ID].worker_id,
});
_session.DS_GLB[jsonP.dsSession] = jsonP;
if (jsonP.dataSourceSessionGlobal > _session.dataSourceSessionGlobal) {
_session.dataSourceSessionGlobal = jsonP.dataSourceSessionGlobal;
}
return done(SESSION_ID, jsonP.dsSession, true);
});
// Publish the in-flight promise on the existing _ds SYNCHRONOUSLY (no await between the
// top guard-check and here) so a synchronous burst of concurrent create(ds5) calls all
// coalesce onto this one promise. Then clear it once the create settles (resolve OR
// reject) so the next genuine refresh runs normally — only removing the flag if it still
// points at THIS promise, never clobbering a newer in-flight create.
if (_guard_ds) {
_guard_ds._create_in_flight = _create_promise;
_create_promise.finally(() => {
const _s = SESSION_OBJ[SESSION_ID];
const _d = _s?.DS_GLB?.[dataSourceNoP];
if (_d && _d._create_in_flight === _create_promise) {
delete _d._create_in_flight;
}
});
}
return _create_promise;
};
func.datasource.prepare = async function (SESSION_ID, prog_id, dataSourceNoP, parentDataSourceNoP, containerIdP, rowIdP, jobNoP, calling_trigger_prop, parameters_raw_obj, NA_isInitP, callingSourceP, calling_jobP, NA_screen_dsP, is_panelP, parameters_obj_inP, static_refreshP, run_atP, worker_id) {
const set_parameters = async function () {
var _session = SESSION_OBJ[SESSION_ID];
const get_Out_parameters = async function (fieldIdP, located_field_param_idxP, param_row_idP) {
var ret = parameters_obj_inP?.[fieldIdP] || fieldIdP;
PARAM_OUT_INFO[prog_id + '_' + param_row_idP] = {
module: _ds.viewModule,
action: 'parameters',
prop: 'out',
details: ret,
result: ret,
source: _ds.viewSourceDesc,
type: 'parameters',
prog_id: prog_id,
dsSession: dataSourceSession,
fieldId: fieldIdP,
parentDataSourceNo: parentDataSourceNoP,
};
return ret;
};
// set parameters list
const screenInfo = await func.utils.get_screen_obj(SESSION_ID, prog_id); // locate root receiving parameters screen id - get root screen info
if (
screenInfo?.properties?.progParams
// &&
// glb.PARAMETER_NODES_ARR.includes(screenInfo.properties.menuType)
) {
if (!xu_isEmpty(screenInfo.properties.progParams)) {
_ds.in_parameters = {};
_ds.out_parameters = {};
for await (let [key, val] of Object.entries(screenInfo.properties?.progParams)) {
// run on parameters arr
if (val.data.dir === 'in') {
_ds.in_parameters[val.data.parameter] = {
// value: parameters_obj_inP?.[val.data.parameter],
type: val.data.type,
};
if (typeof parameters_obj_inP?.[val.data.parameter] !== 'undefined') {
_ds.in_parameters[val.data.parameter].value = parameters_obj_inP[val.data.parameter];
} else if (['live_preview', 'miniapp'].includes(_session.engine_mode)) {
_ds.in_parameters[val.data.parameter].value = _session?.url_params?.[val.data.parameter];
}
continue;
}
if (val.data.dir === 'out' && val.data.parameter) {
_ds.out_parameters[val.data.parameter] = await get_Out_parameters(val.data.parameter, key, val.id);
}
}
_ds.PARAM_OUT_INFO = PARAM_OUT_INFO;
}
}
};
const build_GLOBAL_SYS_fields = function () {
if (!_ds.data_system) _ds.data_system = {};
_ds.data_system['SYS_GLOBAL_UTC'] = -new Date().getTimezoneOffset() / 60;
_ds.data_system['SYS_GLOBAL_STR_APP_ID'] = APP_OBJ[_session.app_id]._id;
_ds.data_system['SYS_GLOBAL_STR_SESSION_ID'] = SESSION_ID;
_ds.data_system['SYS_GLOBAL_STR_LOGIN_USER_ID'] = _session.USR_OBJ._id;
if (!['live_preview', 'miniapp'].includes(_session.engine_mode) && PROJECT_OBJ[_session.app_id].info) {
_ds.data_system['SYS_GLOBAL_OBJ_APP_INFO'] = {
build: PROJECT_OBJ[_session.app_id].info.build_id,
author: PROJECT_OBJ[_session.app_id].info.author,
date: PROJECT_OBJ[_session.app_id].info.build_date,
name: APP_OBJ[_session.app_id].app_name,
};
}
_ds.data_system['SYS_GLOBAL_OBJ_LOGIN_USER_INFO'] = {
id: _session.USR_OBJ._id,
user_name: _session.USR_OBJ.usr_name,
first_name: _session.USR_OBJ.usr_first_name,
last_name: _session.USR_OBJ.usr_last_name,
email: _session.USR_OBJ.usr_email,
profile_picture: _session.USR_OBJ.usr_profile_picture,
};
_ds.data_system['SYS_GLOBAL_STR_BROWSER_HASH_ID'] = _session.SYS_GLOBAL_STR_BROWSER_HASH_ID;
_ds.data_system['SYS_GLOBAL_STR_BROWSER_TITLE'] = _session.SYS_GLOBAL_STR_BROWSER_TITLE;
// }
_ds.data_system['SYS_GLOBAL_STR_SITE_CSS'] = {};
_ds.data_system['SYS_GLOBAL_BOL_SHIFT_KEY_STATE'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_COMMAND_KEY_STATE'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_CONTROL_KEY_STATE'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_ALT_KEY_STATE'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_ONLINE'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_REPLICATION_STAT'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_AJAX_BUSY'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_CONNECTED'] = 1;
_ds.data_system['SYS_GLOBAL_BOL_IDLE'] = 0;
_ds.data_system['SYS_GLOBAL_STR_FIREBASE_TOKEN_ID'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_PUSH_NOTIFICATION_GRANTED'] = _session.PUSH_NOTIFICATION_GRANTED;
_ds.data_system['SYS_GLOBAL_OBJ_FIREBASE_AUTH_INFO'] = _session.SYS_GLOBAL_OBJ_FIREBASE_AUTH_INFO;
_ds.data_system['SYS_GLOBAL_OBJ_CLIENT_INFO'] = _session.SYS_GLOBAL_OBJ_CLIENT_INFO;
_ds.data_system['SYS_GLOBAL_OBJ_REFS'] = {};
};
if (!SESSION_OBJ[SESSION_ID].DS_GLB) return;
if (dataSourceNoP && !SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceNoP]) {
return func.utils.debug_report(SESSION_ID, 'Datasource', 'Datasource not exist: ' + dataSourceNoP, 'E');
}
if (!prog_id) {
return func.utils.debug_report(SESSION_ID, 'Datasource', 'Program is null', 'E');
}
const args = {
SESSION_ID,
prog_id,
dataSourceNoP,
parentDataSourceNoP,
containerIdP,
rowIdP,
jobNoP,
calling_trigger_prop,
calling_jobP,
is_panelP,
parameters_obj_inP,
static_refreshP,
run_atP,
worker_id,
parameters_raw_obj,
};
var dataSourceSession = null;
var IS_DATASOURCE_REFRESH = null;
var PARAM_OUT_INFO = {};
const init_dataSource = async function () {
const init_new_dataSource = async function () {
if (!['main'].includes(SESSION_OBJ[SESSION_ID].opt.app_computing_mode) && run_atP === 'client' && prog_id !== 'system') {
const ret = await func.index.call_worker(SESSION_ID, {
service: 'get_dataSourceSessionGlobal',
data: { session_id: SESSION_ID },
id: SESSION_OBJ[SESSION_ID].worker_id,
});
SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal = ret?.new_dataSourceSessionGlobal || 1;
} else {
SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal++;
}
dataSourceSession = SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal;
SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession] = {
data_feed: { rows: [] },
};
};
const init_existing_dataSource = function () {
let _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceNoP];
console.log('DATASOURCE_REFRESH', dataSourceNoP);
if (!_ds) {
return;
}
IS_DATASOURCE_REFRESH = true;
dataSourceSession = dataSourceNoP;
_ds.refreshed = true;
if (_ds.watcher) {
xu_set(_ds, _ds.watcher.path, _ds.watcher.newValue);
}
try {
if (!_ds.v) _ds.v = {};
delete _ds.v.old_dataSource; // to eliminate parse error
delete _ds.rows_found;
try {
if (_ds.data_feed && _ds.data_feed.rows && _ds.data_feed.rows[0]) {
func.datasource.__vf_preserve.set(_ds, Object.assign({}, _ds.data_feed.rows[0]));
}
} catch (e) {}
// B1: keep the full pre-refresh row set so the render layer can detect a no-op
// refresh (identical rows) and skip rebuilding the list (see render_multi_view_node).
_ds.__refresh_prev_rows = _ds.data_feed && Array.isArray(_ds.data_feed.rows) ? _ds.data_feed.rows : null;
_ds.data_feed = {};
_ds.v.old_dataSource = {
currentRecordId: _ds.currentRecordId,
firstRecordId: _ds.firstRecordId,
finalRecordId: _ds.finalRecordId,
locatedRecordId: _ds.locatedRecordId,
sortOrder: _ds.sortOrder,
sortOrderTypeExp: _ds.sortOrderTypeExp,
};
} catch (err) {
console.error('function: init_existing_dataSource - error', err);
}
};
if (typeof dataSourceNoP === 'undefined' || dataSourceNoP === null) {
await init_new_dataSource();
} else {
init_existing_dataSource();
}
return SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
};
var _ds = await init_dataSource();
if (!_ds) {
return func.utils.debug_report(SESSION_ID, 'Datasource', 'Datasource refresh failed: ' + dataSourceNoP, 'E');
}
_ds.stat = 'busy';
_ds._run_at = run_atP;
if (_ds.refreshed) {
await func.datasource.update(SESSION_ID, {
[_ds.dsSession]: {
['datasource_main']: {
stat: 'busy',
stat_ts: Date.now(),
is_worker: glb.IS_WORKER,
},
},
});
}
// init_v();
if (IS_DATASOURCE_REFRESH) {
if (!static_refreshP) await set_parameters(); // do only on new datasource or refresh
return func.datasource.execute(SESSION_ID, dataSourceSession, true);
}
_ds.tree_obj = await func.utils.TREE_OBJ.get(SESSION_ID, prog_id);
if (!_ds.tree_obj) {
return func.utils.debug_report(SESSION_ID, 'Datasource', 'Program not exist: ' + prog_id, 'E');
}
await func.datasource.set_VIEW_data(SESSION_ID, args, _ds);
if (
!_ds.v.viewSourceDesc // in case of event
) {
_ds.v.viewSourceDesc = callingSourceP;
}
if (dataSourceSession === 0) _ds.v.viewSourceDesc = 'system startup';
var _session = SESSION_OBJ[SESSION_ID];
const set_DS_GLB = async function () {
_ds.dataSource_init_arr = {};
_ds.containerId = containerIdP;
_ds.jobNoP = jobNoP;
_ds.viewSourceDesc = _ds.v.viewSourceDesc;
_ds.callingSource = callingSourceP;
_ds.calling_jobP = calling_jobP;
_ds.viewModule = _ds.v.viewModule;
_ds.viewSourceProp = _ds.v.viewSourceProp;
_ds.dsSession = dataSourceSession;
// _ds.v = v;
_ds.args = args;
_ds.worker_id = worker_id;
_ds.prog_id = prog_id;
// if (!IS_DATASOURCE_REFRESH) {
_ds.parentDataSourceNo = parentDataSourceNoP;
// }
};
await set_DS_GLB();
if (
prog_id === 'system' &&
!parentDataSourceNoP // do only on first time datasource 0 call
) {
build_GLOBAL_SYS_fields();
}
// ======================================
// PARAMETERS
//======================================
await set_parameters();
// ======================================
// INTERVALS
//======================================
_ds.client_interval = func.datasource.get_event_interval_arr(SESSION_ID, dataSourceSession, 'client_interval');
if (prog_id === 'system') {
_ds.server_interval = func.datasource.get_event_interval_arr(SESSION_ID, dataSourceSession, 'server_interval');
}
// if (static_refreshP) {
// delete _ds.raw_data;
// }
// if (!static_refreshP) await set_parameters(); // do only on new datasource or refresh
// try {
let ret_execute = await func.datasource.execute(SESSION_ID, dataSourceSession);
return ret_execute;
// } catch (e) {
// console.error(e);
// }
};
func.datasource.execute = async function (SESSION_ID, dataSourceSession, IS_DATASOURCE_REFRESH) {
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dataSourceSession];
var args = _ds.args;
// var v = _ds.v;
let tree_obj = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id);
let prog_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
const normalize_filter_model = function (value) {
if (value === null || typeof value === 'undefined') {
return undefined;
}
if (typeof value === 'string') {
const trimmed = value.trim();
if (!trimmed) {
return undefined;
}
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
try {
return JSON.parse(trimmed);
} catch (err) {
return value;
}
}
}
return value;
};
const callback_datasource = async function () {
const run_on_load_events = async function () {
if (!(await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'on_load'))) {
return false;
}
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'on_load');
return true;
};
const schedule_panel_on_load_events = function () {
setTimeout(async function () {
try {
await run_on_load_events();
} catch (error) {
console.error(error);
}
}, 0);
};
if (typeof IS_WORKER === 'undefined' && typeof IS_DOCKER === 'undefined' && typeof IS_PROCESS_SERVER === 'undefined' && _ds.viewSourceProp === 'globals') {
if (!['main'].includes(_session.opt.app_computing_mode)) {
await func.index.call_worker(SESSION_ID, {
service: 'create_webworker_globals',
data: { ds_data: _ds, session_id: SESSION_ID },
});
}
}
// on_load view events fire on INITIAL load only, never on a datasource REFRESH —
// the same once-per-screen rule as screen_ready. Re-firing on every refresh lets an
// on_load event that raises a render/fetch event (e.g. RENDER_PAGE_EVENT -> get_data +
// field update) re-render the screen, which refreshes the datasource, which re-fires
// on_load -> ... an infinite render loop (the panel "flicker"). A refresh reuses the
// same datasource session (IS_DATASOURCE_REFRESH); navigation creates a new one and
// fires on_load there as expected.
if (args.is_panelP) {
const callback_ret = await func.datasource.callback(SESSION_ID, dataSourceSession, args.rowIdP, args.jobNoP, _ds.prog_id);
if (!IS_DATASOURCE_REFRESH) schedule_panel_on_load_events();
return callback_ret;
}
if (!IS_DATASOURCE_REFRESH) await run_on_load_events();
return await func.datasource.callback(SESSION_ID, dataSourceSession, args.rowIdP, args.jobNoP, _ds.prog_id);
};
const get_limit = async function () {
var ret = 0;
let tree_ret = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id);
if (tree_ret.menuType === 'get_data') {
return 1;
}
ret = _ds.progDataSource?.dataSourceLimit;
if (prog_obj.progDataSource?.dataSourceLoopExp) {
ret = (await func.expression.get(SESSION_ID, prog_obj.progDataSource.dataSourceLoopExp, dataSourceSession, 'view_loop', args.rowIdP)).result;
}
return ret;
};
const get_skip = async function () {
var ret = 0;
ret = _ds.progDataSource?.dataSourceSkip;
if (prog_obj.progDataSource?.dataSourceSkipExp) {
ret = (await func.expression.get(SESSION_ID, prog_obj.progDataSource.dataSourceSkipExp, dataSourceSession, 'view_loop', args.rowIdP)).result;
}
return ret;
};
const calc_batch_loops = async () => {
if (!prog_obj.progDataSource?.dataSourceType || _ds.progDataSource.dataSourceType === 'none') {
_ds.v.batch_loops = await get_limit();
return false;
}
_ds.v.batch_loops = (await get_limit()) <= _ds.v.raw_data?.rows?.length ? await get_limit() : _ds.v.raw_data?.rows?.length;
return true;
};
const render_api_output = async function () {
if (prog_obj?.scriptData?.value) {
var exp = await func.expression.get(SESSION_ID, prog_obj.scriptData.value, dataSourceSession, 'api_rendered_output', null, null, null, null, null, null, null, null, null, tree_obj.apiOutput);
let output_result = exp.result;
if (tree_obj.apiOutput === 'json') {
// iterate object to fix expressions
try {
// let output_result_obj = JSON5.parse(output_result)
let output_result_obj = await func.expression.secure_eval(SESSION_ID, 'api_rendered_output', '(' + output_result + ')', null, dataSourceSession);
// for await (let [key, val] of Object.entries(output_result_obj)) {
// output_result_obj[key] = (await func.expression.get(
// SESSION_ID,
// val,
// dataSourceSession, "object_property")).result
// }
output_result = JSON.stringify(output_result_obj);
} catch (err) {
console.error(err);
}
}
_ds.api_rendered_output += output_result + (tree_obj.apiOutput === 'json' ? ',' : '');
} else {
_ds.api_rendered_output = ''; //empty
}
};
// ======================================
// COMPUTE GLOBALS
// ======================================
if (_ds.prog_id === 'system') {
//TBD tree_obj.menuType === "globals"
_ds.currentRecordId = 'dataset';
await func.datasource.render_fields_dataset(SESSION_ID, dataSourceSession, {
id: 'dataset',
value: _session.url_params,
});
return await callback_datasource();
}
// ======================================
// BUILD DATA SOURCE
//======================================
let db_adapter_module;
if (prog_obj.progDataSource?.dataSourceType) {
db_adapter_module = await func.common.get_module(SESSION_ID, 'xuda-datasource-db-adapter-module.mjs');
}
const get_data_from_source = async function () {
switch (prog_obj.progDataSource.dataSourceSrcType) {
case 'input': {
const { result, error } = await func.expression.get(SESSION_ID, prog_obj.progDataSource.progDataSourceInput, dataSourceSession, 'datasource select');
if (error) {
func.utils.debug_report(SESSION_ID, 'Data source', `Datasource parse error using ${prog_obj.progDataSource?.dataSourceType} input`, 'E');
return null;
}
return result;
break;
}
case 'url': {
let opt = {
method: prog_obj.progDataSource.dataSourceMethod || 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
};
let data = {};
if (prog_obj.progDataSource.dataSourceMethod == 'POST' && prog_obj.progDataSource.dataSourceParameters) {
for (let val of prog_obj.progDataSource.dataSourceParameters) {
data[val.key] = val.val;
}
opt.body = JSON.stringify(data);
}
try {
const response = await fetch('https://' + prog_obj.progDataSource.dataSourceDataUrl, opt);
const json = await response.json();
return json.data;
} catch (err) {
func.utils.debug_report(SESSION_ID, 'Data source', err.message + ' https://' + prog_obj.progDataSource.dataSourceDataUrl, 'E');
return null;
}
break;
}
default:
return null;
break;
}
};
if (!_ds.v.raw_data) {
_ds.v.raw_data = { rows: [] };
}
_ds.data_feed.rows = [];
switch (prog_obj.progDataSource?.dataSourceType) {
case 'table': {
_ds._dataSourceTableId = prog_obj.progDataSource?.dataSourceTableId; // get file id
if (prog_obj.progDataSource?.dataSourceTableIdExp) {
_ds.v.dataSourceTableIdExp = await func.expression.get(SESSION_ID, prog_obj.progDataSource?.dataSourceTableIdExp, dataSourceSession, 'dataSourceTableIdExp', args.rowIdP);
if (_ds.v.dataSourceTableIdExp.result) {
_ds._dataSourceTableId = _ds.v.dataSourceTableIdExp.result;
} else {
func.utils.debug_report(SESSION_ID, 'get_VIEW_data', 'Table Expression returned empty result', 'W');
}
}
if (!_ds._dataSourceTableId) {
return func.utils.debug_report(SESSION_ID, 'Data source', 'Table cannot be empty when Db Table selected', 'E');
}
let table_ret = await func.utils.TREE_OBJ.get(SESSION_ID, _ds._dataSourceTableId);
if (!table_ret) {
return func.utils.debug_report(SESSION_ID, 'Data source', 'Table not found: ' + _ds._dataSourceTableId, 'E');
}
await db_adapter_module.build_filter(SESSION_ID, dataSourceSession, _ds.v, _ds);
let filterModelMongo = _ds.progDataSource.filterModelMongo;
if (_ds.progDataSource.filterModelMongoFx) {
let ret = await func.expression.get(SESSION_ID, _ds.progDataSource.filterModelMongoFx, dataSourceSession, 'query', _ds.args.rowIdP);
filterModelMongo = ret.result;
}
filterModelMongo = normalize_filter_model(filterModelMongo);
let filterModelSql = _ds.progDataSource.filterModelSql;
if (_ds.progDataSource.filterModelSqlFx) {
let ret = await func.expression.get(SESSION_ID, _ds.progDataSource.filterModelSqlFx, dataSourceSession, 'query', _ds.args.rowIdP);
filterModelSql = ret.result;
}
filterModelSql = normalize_filter_model(filterModelSql);
const filterModel = {
filterModelNative: normalize_filter_model(_ds.progDataSource.filterModelNative),
filterModelMongo,
filterModelSql,
filterModelUserMongo: normalize_filter_model(_ds.progDataSource.filterModelUserMongo),
filterModelUserSql: normalize_filter_model(_ds.progDataSource.filterModelUserSql),
};
let _dataSourceFilterModelType = _ds?.progDataSource?.dataSourceFilterModelType;
if (_ds?.progDataSource?.dataSourceFilterModelTypeFx) {
const fx_ret = await func.expression.get(SESSION_ID, _ds.progDataSource.dataSourceFilterModelTypeFx, dataSourceSession, 'query', _ds.args.rowIdP);
_dataSourceFilterModelType = fx_ret.result;
}
// Infer 'index' from a configured index when no explicit type is set, so
// index datasources without an explicit dataSourceFilterModelType still
// apply their index filter. Prefer the value build_filter already resolved
// on _ds.v (it sees the full progDataSource incl. the index id), then fall
// back to inferring from the index id directly.
_dataSourceFilterModelType =
_dataSourceFilterModelType ||
_ds?.v?.dataSourceFilterModelType ||
(_ds?.progDataSource?.dataSourceIndexId || _ds?.progDataSource?.dataSourceIndexIdExp ? 'index' : 'query');
if (_dataSourceFilterModelType && !['query', 'index'].includes(_dataSourceFilterModelType)) {
return func.utils.debug_report(SESSION_ID, 'Data source', `Valid values for dataSourceFilterModelType are: "query" or "index" (${_dataSourceFilterModelType})`, 'E');
}
const sortModel = Array.isArray(_ds?.progDataSource?.sortModel) ? _ds.progDataSource.sortModel : [];
// An EMPTY sortModel must be sent as null, not []. get_query does
// `if (sortModel) data.sortModel = JSON.stringify(sortModel)` and [] is
// truthy, so it forwards sortModel:"[]" to the backend, which overrides the
// configured index's natural key order with an unsorted (_id) order. Index
// datasources that rely on their index ordering (no explicit sort model)
// were rendering unsorted. Only send a real, non-empty sort model.
const sortModelForDb = _dataSourceFilterModelType === 'query' || !sortModel.length ? null : sortModel;
_ds.v.raw_data = await func.db.get_query(
SESSION_ID,
_ds._dataSourceTableId,
_ds.v.couchView,
dataSourceSession,
_ds.viewSourceDesc,
'datasource table',
prog_obj.progDataSource.dataSourceReduce,
await get_skip(),
(await get_limit()) || 99999999,
null,
null,
sortModelForDb,
null,
filterModel,
_dataSourceFilterModelType,
);
if (sortModel.length && _ds?.v?.raw_data?.rows?.length) {
function sortByKeys(array, sortConfig) {
return array.sort((a, b) => {
for (let config of sortConfig) {
const key = config.field_id || config.colId;
const direction = (config.sort_dir || config.sort) === 'desc' ? -1 : 1;
const valA = a.value[key];
const valB = b.value[key];
if (typeof valA === 'undefined' && typeof valB === 'undefined') {
continue;
}
if (typeof valA === 'undefined') {
return 1;
}
if (typeof valB === 'undefined') {
return -1;
}
// Handle numeric comparison
if (typeof valA === 'number' && typeof valB === 'number') {
if (valA !== valB) {
return (valA - valB) * direction;
}
}
// Handle string comparison
else if (typeof valA === 'string' && typeof valB === 'string') {
if (valA !== valB) {
return valA.localeCompare(valB) * direction;
}
} else if (valA !== valB) {
return String(valA).localeCompare(String(valB)) * direction;
}
}
return 0;
});
}
const sorted = sortByKeys(_ds.v.raw_data.rows, _ds.progDataSource.sortModel);
_ds.v.raw_data.rows = sorted;
}
if (_ds?.progDataSource?.dataSourceLimit) {
const ret_rows_found = await func.db.get_query(SESSION_ID, _ds._dataSourceTableId, _ds.v.couchView, dataSourceSession, _ds.viewSourceDesc, 'datasource table', prog_obj.progDataSource.dataSourceReduce, null, null, true, null, null, null, filterModel, _dataSourceFilterModelType);
_ds.rows_found = ret_rows_found?.rows?.[0]?.value || 0;
_ds.rows_found_opt = ret_rows_found?.opt;
} else {
_ds.rows_found = _ds?.v?.raw_data?.rows?.length || 0;
_ds.rows_found_opt = _ds?.v?.raw_data?.opt;
}
break;
}
case 'array': {
let data = await get_data_from_source();
if (data === null) {
data = [];
}
_ds.rows_found = data?.length || 0;
let _KEY = 0;
for (const _VAL of data) {
_ds.v.raw_data.rows.push({ id: _KEY, value: { _KEY, _VAL } });
_KEY++;
}
break;
}
case 'json': {
let data = await get_data_from_source();
if (data === null) {
data = {};
}
_ds.rows_found = Object.keys(data)?.length || 0;
for (let [_KEY, _VAL] of Object.keys(data)) {
_ds.v.raw_data.rows.push({ id: _KEY, value: { _KEY, _VAL } });
}
break;
}
case 'csv': {
let data = await get_data_from_source();
if (data === null) {
data = '';
}
let _KEY = 0;
let arr = data.split(',');
for (const _VAL of arr) {
_ds.v.raw_data.rows.push({ id: _KEY, value: { _KEY, _VAL } });
_KEY++;
}
_ds.rows_found = arr?.length || 0;
break;
}
default:
break;
}
// ======================================
// EXECUTE
// ======================================
let ret;
const get_before_record_count = async () => {
return await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'before_record');
};
const get_after_record_count = async () => {
return await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'after_record');
};
let _raw_data_rows = [];
switch (tree_obj.menuType) {
case 'api': {
_ds.api_rendered_output = '';
let has_datasource = await calc_batch_loops();
_raw_data_rows = _ds.v.raw_data.rows || [];
if (!has_datasource) {
for (n = 0; n < _ds.v.batch_loops; n++) {
_raw_data_rows.push({ id: n, value: {} });
}
}
_ds.currentRecordId = 'dataset';
for await (let [key, raw_data_row] of Object.entries(_raw_data_rows)) {
if (has_datasource && Number(key) >= _ds.v.batch_loops) break;
if (!has_datasource) {
raw_data_row = _ds?.v.raw_data?.rows?.[key] || { id: key, value: {} };
}
if (await get_before_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'before_record');
}
await func.datasource.render_fields_dataset(SESSION_ID, dataSourceSession, raw_data_row);
if (await get_after_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
}
await render_api_output();
}
if (tree_obj.apiOutput === 'json') {
var str = _ds.api_rendered_output.substring(0, _ds.api_rendered_output.length - 1);
if (Number(_ds.progDataSource?.dataSourceLimit) === 1) {
_ds.api_rendered_output = str;
} else {
_ds.api_rendered_output = '[' + str + ']';
}
}
break;
}
case 'batch': {
let has_datasource = await calc_batch_loops();
_raw_data_rows = _ds?.v.raw_data?.rows || [];
if (!has_datasource) {
for (n = 0; n < _ds.v.batch_loops; n++) {
_raw_data_rows.push({ id: n, value: {} });
}
}
_ds.currentRecordId = 'dataset';
for await (let [key, raw_data_row] of Object.entries(_raw_data_rows)) {
if (has_datasource && Number(key) >= _ds.v.batch_loops) break;
if (!has_datasource) {
raw_data_row = _ds?.v.raw_data?.rows?.[key] || { id: key, value: {} };
}
if (await get_before_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'before_record');
}
await func.datasource.render_fields_dataset(SESSION_ID, dataSourceSession, raw_data_row);
if (await get_after_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
}
}
await func.datasource.set_outputField(SESSION_ID, dataSourceSession, _ds?.v?.raw_data?.rows, _ds.args);
break;
}
case 'get_data': {
if (await get_before_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'before_record');
}
ret = await db_adapter_module.process_view_dataset(SESSION_ID, dataSourceSession, _ds);
if (await get_after_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
}
await func.datasource.set_outputField(SESSION_ID, dataSourceSession, _ds?.v?.raw_data?.rows, _ds.args);
break;
}
case 'set_data': {
if (!prog_obj.progDataSource?.dataSourceType || _ds.progDataSource.dataSourceType !== 'table' || !_ds._dataSourceTableId) {
return func.utils.debug_report(SESSION_ID, 'Data source', 'Datasource DB Table must be defined for Set Data operation', 'E');
}
const find_ROWID_idx_from_raw_data_arr = function (rowId) {
if (!_raw_data_rows) {
throw new Error('_raw_data_rows not found');
}
const index = _raw_data_rows.findIndex((item) => item.id === rowId);
if (index === -1) {
throw new Error(`ROWID "${rowId}" not found`);
}
return index;
};
if (tree_obj.crudMode === 'U') {
_ds.set_mode = 'U';
_raw_data_rows = _ds?.v.raw_data?.rows || [];
}
if (tree_obj.crudMode === 'D') {
_ds.set_mode = 'D';
_raw_data_rows = _ds?.v.raw_data?.rows || [];
}
// initiated with Update but no rows found
if (tree_obj.crudMode === 'U' && tree_obj.allowCreate && !_raw_data_rows?.length) {
_ds.set_mode = 'C';
try {
const row_idx = find_ROWID_idx_from_raw_data_arr('newRecord');
_raw_data_rows[row_idx] = { id: 'newRecord', value: {} };
} catch (error) {
_raw_data_rows.push({ id: 'newRecord', value: {} });
}
}
if (tree_obj.crudMode === 'C') {
_ds.set_mode = 'C';
try {
const row_idx = find_ROWID_idx_from_raw_data_arr('newRecord');
_raw_data_rows[row_idx] = [{ id: 'newRecord', value: {} }];
} catch (error) {
_raw_data_rows.push({ id: 'newRecord', value: {} });
}
}
const trace_set_data = function (label, payload) {
if (String(_ds?.prog_id || '') !== '1630849293262' && String(_ds?._dataSourceTableId || '') !== '1630542401398') return;
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] set_data_trace ' +
JSON.stringify({
version: 'runtime-refresh-20260629-set-data-remote-save',
label,
dataSourceSession,
prog_id: _ds?.prog_id,
table_id: _ds?._dataSourceTableId,
set_mode: _ds?.set_mode,
currentRecordId: _ds?.currentRecordId,
...payload,
}),
);
} catch (err) {
console.warn('[xuda-runtime] set_data_trace_failed', err);
}
};
trace_set_data('start', {
crudMode: tree_obj.crudMode,
allowCreate: tree_obj.allowCreate,
raw_rows_length: _raw_data_rows?.length || 0,
data_feed: func.datasource._debug_summarize_set_data_feed(_ds.data_feed),
});
for await (let raw_data_row of _raw_data_rows) {
_ds.currentRecordId = raw_data_row.id;
let data_feed_str = JSON.stringify(_ds.data_feed);
trace_set_data('before_record', {
raw_row_id: raw_data_row.id,
data_feed: func.datasource._debug_summarize_set_data_feed(_ds.data_feed),
});
if (await get_before_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'before_record');
}
await func.datasource.render_fields_dataset(SESSION_ID, dataSourceSession, raw_data_row);
if (await get_after_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
}
const data_feed_after_str = JSON.stringify(_ds.data_feed);
const should_save = _ds.set_mode === 'C' || data_feed_after_str !== data_feed_str;
trace_set_data('save_decision', {
should_save,
changed: data_feed_after_str !== data_feed_str,
data_feed: func.datasource._debug_summarize_set_data_feed(_ds.data_feed),
});
if (should_save) {
const dbMsgP = await func.db.save_data(SESSION_ID, dataSourceSession);
trace_set_data('save_result', {
result: dbMsgP
? {
code: dbMsgP.code,
id: dbMsgP.id || dbMsgP.data?.id,
data_id: dbMsgP.data?.id,
message: dbMsgP.message,
}
: null,
});
if (dbMsgP) _ds.currentRecordId = dbMsgP.id;
_ds.set_mode = 'U';
}
if (_ds.set_mode === 'D') {
const dbMsgP = await func.db.save_data(SESSION_ID, dataSourceSession);
if (dbMsgP) _ds.currentRecordId = dbMsgP.id;
}
}
await func.datasource.set_outputField(SESSION_ID, dataSourceSession, _ds?.v?.raw_data?.rows, _ds.args);
break;
}
case 'component': {
_raw_data_rows = _ds?.v.raw_data?.rows || [];
_ds.rows_processed = 0;
_ds.viewRangeExp_rows_deleted = 0;
let rows = _ds?.v.raw_data?.rows?.length;
_ds.data_feed.rows_changed = [];
_ds.data_feed.rows_deleted = [];
_ds.data_feed.rows_added = [];
if (tree_obj.rwMode === 'U') {
_ds.set_mode = 'U';
} else {
_ds.set_mode = 'R';
}
const row_not_found = async function () {
if (!prog_obj.progDataSource?.dataSourceType || prog_obj.properties.renderType === 'form' || (tree_obj.rwMode === 'U' && tree_obj.allowCreate)) {
_ds.currentRecordId = 'newRecord';
if (tree_obj.rwMode === 'U' && tree_obj.allowCreate) {
_ds.set_mode = 'C';
}
if (prog_obj.progDataSource?.dataSourceType) {
_ds.record_not_found = true;
}
try {
const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
} catch (error) {
await func.datasource.render_fields_form(SESSION_ID, dataSourceSession, { id: 'newRecord', value: {} });
}
var count = await func.datasource.get_field_init_count(SESSION_ID, dataSourceSession, 'newRecord', false);
if (
count > 0 // was: && !args.dataSourceNoP
) {
await func.datasource.execute_field_init_events(SESSION_ID, dataSourceSession, 'form', 'newRecord');
}
} else {
_ds.record_not_found = true;
delete _ds.currentRecordId;
delete _ds.firstRecordId;
delete _ds.finalRecordId;
delete _ds.locatedRecordId;
}
await func.datasource.callback(SESSION_ID, dataSourceSession, args.rowIdP, args.jobNoP, _ds.prog_id);
};
if (!rows) {
await row_not_found();
break;
}
_ds.firstRecordId = _raw_data_rows[0].id;
const finish_form = async function () {
// check if locatedRecordId exist in SESSION_OBJ[SESSION_ID].DS_GLB
if (_ds.locatedRecordId) {
try {
const row_idx = func.common.find_ROWID_idx(_ds, _ds.locatedRecordId);
} catch (error) {
delete _ds.locatedRecordId;
}
}
_ds.finalRecordId = func.datasource.get_currentRecordId(SESSION_ID, dataSourceSession, true);
_ds.currentRecordId = _ds.finalRecordId;
};
for await (const [key, raw_data_row] of Object.entries(_raw_data_rows)) {
const idx = Number(key);
if (await get_before_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'before_record');
}
_ds.currentRecordId = raw_data_row.id; // set temporary to allow expression decoder work
await func.datasource.render_fields_form(SESSION_ID, dataSourceSession, raw_data_row);
try {
const init_count = await func.datasource.get_field_init_count(SESSION_ID, dataSourceSession, raw_data_row.id, false, _ds.oninit_triggers_to_run);
if (init_count > 0) {
await func.datasource.execute_field_init_events(SESSION_ID, dataSourceSession, 'form', raw_data_row.id);
}
} catch (err) {
console.error(err);
}
if (await get_after_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
}
}
await finish_form();
break;
}
default:
return func.utils.debug_report(SESSION_ID, 'Data source', 'Program type not defined', 'E');
}
ret = await callback_datasource();
return ret;
};
func.datasource.render_fields_dataset = async function (SESSION_ID, dataSourceSession, raw_data_row) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
const _progFields = await func.datasource.get_progFields(SESSION_ID, dataSourceSession);
if (!_progFields) {
return;
}
const get_value = async (field_id, value) => {
let view_field_obj = func.common.find_item_by_key(_progFields, 'field_id', field_id);
var fieldType = view_field_obj?.props?.fieldType;
let table_field_obj;
if (view_field_obj.data.type === 'table' && field_id !== 'REDUCE_VALUE') {
if (!_ds.progDataSource?.dataSourceTableId) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `Table type defined without dataSourceTableId deceleration`, 'E');
}
let table_obj = await func.utils.FILES_OBJ.get(SESSION_ID, _ds._dataSourceTableId);
if (!table_obj) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `dataSourceTableId reference error: ` + _ds._dataSourceTableId, 'E');
}
table_field_obj = func.common.find_item_by_key(table_obj.tableFields, 'field_id', field_id);
fieldType = table_field_obj.props?.fieldType;
}
return await func.common.get_cast_val(SESSION_ID, `render fields dataset ${_ds.viewSourceDesc}`, field_id, fieldType, value, null);
};
if (!_ds.data_feed) {
_ds.data_feed = { rows: [{ _ROWID: _ds.currentRecordId }] };
}
let row_idx;
try {
row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
} catch (err) {
_ds.data_feed.rows.push({ _ROWID: _ds.currentRecordId });
row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
// console.error(err);
}
_ds.dataset_alias = {};
for await (const val of _progFields) {
try {
var fieldId = val.data.field_id;
if (val.data.type === 'virtual' || _ds.set_mode === 'C') {
if (typeof raw_data_row?.value?.[fieldId] !== 'undefined') {
// supports x=x+1
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, raw_data_row.value[fieldId]);
continue;
}
if (val.props?.propExpressions?.fieldValue) {
let ret = await func.expression.get(SESSION_ID, val.props?.propExpressions?.fieldValue, dataSourceSession, 'update', args.rowIdP);
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, ret.result);
continue;
}
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, val.props?.fieldValue);
continue;
}
if (val.data.type === 'table' || val.data.type === 'datasource') {
if (typeof raw_data_row.value[fieldId] === 'undefined') {
throw 'field do not exist in data: ' + fieldId;
}
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, raw_data_row.value[fieldId]);
}
} catch (err) {
func.utils.debug_report(SESSION_ID, 'Datasource', err, 'E', null, _ds);
}
}
};
func.datasource.run_events_functions = async function (SESSION_ID, dataSourceSession, event_id, calling_job, async_event, event_parameters, event_optionsP) {
if (typeof dataSourceSession === 'undefined' || dataSourceSession === null) {
console.warn(`Event ${event_id} not exist or not found`);
return;
}
// return new Promise(async (resolve, reject) => {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
// var v = _ds.v;
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
let job_promises = [];
if (_view_obj.progEvents) {
for await (const [key, val] of Object.entries(_view_obj.progEvents)) {
if (val.data.type === 'user_defined' && val.data.event_name && val.data.event_name === event_id) {
const jobs = await func.events.validate(
SESSION_ID,
'user_defined',
dataSourceSession,
val.data.event_name,
args.callingSourceP,
event_parameters, // val.data.parameters
null,
event_optionsP,
);
if (calling_job || async_event) continue;
for (let job_num of jobs) {
job_promises.push(
new Promise((resolve, reject) => {
let i = 0;
const interval = setInterval(() => {
i++;
var job_index = func.events.find_job_index(SESSION_ID, job_num);
if (job_index == null) {
clearInterval(interval);
resolve(job_num);
}
if (i > 200) {
func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_DSC_050',
source: 'func.datasource.run_events_functions',
message: 'deadlock detected',
type: 'E',
details: {
job_num,
event_id,
dsSessionP,
},
});
clearInterval(interval);
resolve(job_num);
}
}, 100);
}),
);
}
}
}
}
if (job_promises.length) {
await Promise.all(job_promises);
}
};
func.datasource.render_fields_form = async function (SESSION_ID, dataSourceSession, raw_data_row) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
const _progFields = await func.datasource.get_progFields(SESSION_ID, dataSourceSession);
if (!_progFields) {
return;
}
const get_value = async (field_id, value) => {
let view_field_obj = func.common.find_item_by_key(_progFields, 'field_id', field_id);
var fieldType = view_field_obj.props?.fieldType;
let table_field_obj;
if (view_field_obj.data.type === 'table' && field_id !== 'REDUCE_VALUE') {
if (!_ds._dataSourceTableId) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `Table type defined without dataSourceTableId deceleration`, 'E');
}
let table_obj = await func.utils.FILES_OBJ.get(SESSION_ID, _ds._dataSourceTableId);
if (!table_obj) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `dataSourceTableId reference error: ` + _ds._dataSourceTableId, 'E');
}
table_field_obj = func.common.find_item_by_key(table_obj.tableFields, 'field_id', field_id);
if (!table_field_obj) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `Field Id: ${field_id} not exist in table ${table_obj.properties.menuName}`, 'E');
}
fieldType = table_field_obj.props?.fieldType;
}
return await func.common.get_cast_val(SESSION_ID, `render fields datasource ${_ds.viewSourceDesc}`, field_id, fieldType, value, null);
};
let row_idx;
try {
row_idx = func.common.find_ROWID_idx(_ds, raw_data_row.id);
} catch (error) {
_ds.data_feed.rows.push({ _ROWID: raw_data_row.id });
row_idx = func.common.find_ROWID_idx(_ds, raw_data_row.id);
}
_ds.dataset_alias = {};
for await (const val of _progFields) {
try {
var fieldId = val.data.field_id;
if (val.data.type === 'virtual' || raw_data_row.id === 'newRecord') {
if (glb.PROTECTED_VARS.includes(fieldId)) {
switch (fieldId) {
case '_ROWNO': {
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, row_idx);
continue;
}
case '_ROWID': {
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, raw_data_row.id);
continue;
}
case '_ROWDOC': {
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, raw_data_row.value);
continue;
}
}
}
if (val.props?.propExpressions?.fieldValue) {
// check init exp existence
let ret = await func.expression.get(SESSION_ID, val.props?.propExpressions?.fieldValue, dataSourceSession, 'update', raw_data_row.id);
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, ret.result);
continue;
}
const _vfp = func.datasource.__vf_preserve.get(_ds);
const _vfPrev = _vfp ? _vfp[fieldId] : undefined;
_ds.data_feed.rows[row_idx][fieldId] =
_vfPrev !== undefined && _vfPrev !== null && _vfPrev !== ''
? _vfPrev
: await get_value(fieldId, val.props?.fieldValue);
continue;
}
if (val.data.type === 'table' || val.data.type === 'datasource') {
if (typeof raw_data_row.value[fieldId] === 'undefined') {
throw 'field do not exist in data: ' + fieldId;
}
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, raw_data_row.value[fieldId]);
}
} catch (err) {
func.utils.debug_report(SESSION_ID, 'Datasource', err, 'E', null, _ds);
}
}
};
func.datasource.execute_field_init_events = async function (SESSION_ID, dataSourceSession, sourceP, rowIdP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
var arr = _ds.dataSource_init_arr[rowIdP];
// for await (const [key, val] of Object.entries(arr)) {
for await (const val of arr) {
if (!func.utils.is_onscreen_event(val.eventInfo.data.action)) {
var cond = val?.eventInfo?.data?.enabled;
var expression = undefined;
if (val.eventInfo.props.condition) expression = val.eventInfo.props.condition;
var expCond = {};
if (expression && !xu_isEmpty(expression)) {
// check if expression exist
expCond = await func.expression.get(SESSION_ID, expression, dataSourceSession, 'condition', rowIdP, null, null, val.fieldId); // execute expression
cond = expCond.result;
expCond.conditional = true;
val.DEBUG_INFO_OBJ.result = expCond.result;
val.DEBUG_INFO_OBJ.error = expCond.error;
val.DEBUG_INFO_OBJ.fields = expCond.fields;
val.DEBUG_INFO_OBJ.conditional = expCond.conditional;
val.DEBUG_INFO_OBJ.details = expression;
}
func.utils.debug.log(SESSION_ID, val.node_id, val.DEBUG_INFO_OBJ);
if (cond) {
// check condition again
if (!_ds) continue;
var ds = _ds.prog_id;
await func.events.execute(
SESSION_ID,
null,
val.triggerId,
val.eventInfo.data.name, //val.eventInfo.data.trigger,
val.eventInfo.data.action,
val.eventInfo.data.name, //eventInfo[5],
null,
val.fieldId,
val.rowId,
val.colId,
null,
null,
dataSourceSession,
val.eventInfo.id, //eventInfo[2],
sourceP,
true,
null,
null,
args.jobNoP,
null,
null,
val.eventInfo,
null,
null,
null,
null,
ds.parentDataSourceNo,
null,
);
}
}
}
};
func.datasource.get_field_init_count = async function (SESSION_ID, dataSourceSession, rowIdP, pre_initP, oninit_triggers_to_runP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
// _ds.dataSource_init_arr[rowIdP] = {};
var ret = 0;
// if (_ds.v.ViewFieldsObjs) {
for await (const field_obj of _view_obj.progFields) {
var fieldId = field_obj.data.field_id;
if (!field_obj?.workflow?.length) {
continue;
}
for await (const trigger_obj of field_obj.workflow) {
if (oninit_triggers_to_runP && !oninit_triggers_to_runP?.includes(trigger_obj.id)) {
continue;
}
if (
// trigger_obj.data.trigger === "oninit" &&
['get_data', 'set_data', 'batch', 'update', 'raise_event'].includes(trigger_obj.data.action)
) {
if (!trigger_obj.data.action) {
func.utils.debug_report(SESSION_ID, '_ds.get_field_init_count', `Error initiating event for field: ${fieldId} prog: ${_ds.v.viewSourceDesc} row: ${rowIdP} reason: missing action`, 'E');
break;
}
if (trigger_obj.data.enabled) {
if (!_ds.dataSource_init_arr[rowIdP]) {
_ds.dataSource_init_arr[rowIdP] = [];
}
// _ds.dataSource_init_arr[rowIdP][ret] = {
// eventInfo: trigger_obj,
// triggerId: trigger_obj.id,
// fieldId: fieldId,
// rowId: rowIdP,
// colId: field_obj.id,
// node_id: args.prog_id + "_" + trigger_obj.id + "_" + field_obj.id,
// fieldProp: field_obj,
// DEBUG_INFO_OBJ: {
// module: _ds.viewModule,
// action: "init field event",
// prop: fieldId,
// source: _ds.viewSourceDesc,
// type: "event",
// prog_id: args.prog_id,
// dsSession: dataSourceSession,
// },
// };
_ds.dataSource_init_arr[rowIdP].push({
eventInfo: trigger_obj,
triggerId: trigger_obj.id,
fieldId: fieldId,
rowId: rowIdP,
colId: field_obj.id,
node_id: args.prog_id + '_' + trigger_obj.id + '_' + field_obj.id,
fieldProp: field_obj,
DEBUG_INFO_OBJ: {
module: _ds.viewModule,
action: 'init field event',
prop: fieldId,
source: _ds.viewSourceDesc,
type: 'event',
prog_id: args.prog_id,
dsSession: dataSourceSession,
},
});
ret++;
}
}
}
}
// }
return ret;
};
func.datasource.get_view_events_count = async function (SESSION_ID, dataSourceSession, typeP, eventIdP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
const _prog = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
if (!_ds) return 0;
var args = _ds.args;
var index = typeP;
if (eventIdP) index = typeP + '_' + eventIdP;
// client interval
if (!_ds.viewEventExec_arr) _ds.viewEventExec_arr = {};
_ds.viewEventExec_arr[index] = [];
if (!_prog.progEvents || xu_isEmpty(_prog.progEvents)) return 0;
for (const event_obj of _prog.progEvents) {
if (event_obj.data.type !== typeP) continue; // was false?? changed to true 020317
if (eventIdP && event_obj.id !== eventIdP) continue; // match w ID added 03312017
if (event_obj.data.condition) {
let res = await func.expression.get(SESSION_ID, event_obj.data.condition, dataSourceSession, 'condition', args.rowIdP, null, null, null, null, event_obj);
if (!res.result) {
continue;
}
}
if (xu_isEmpty(event_obj.workflow)) continue;
for (const trigger_obj of event_obj.workflow) {
if (trigger_obj.data.enabled) {
var expression;
if (trigger_obj.props.condition) expression = trigger_obj.props.condition;
var expCond = {};
if (expression) {
expCond.conditional = true;
}
func.utils.debug.log(SESSION_ID, args.prog_id + '_' + trigger_obj.id, {
module: _ds.viewModule,
action: trigger_obj.data.action,
prop: event_obj.data.type,
details: expression,
result: expCond.result,
error: expCond.error,
source: _ds.viewSourceDesc,
fields: expCond.fields,
type: 'event',
prog_id: args.prog_id,
dsSession: dataSourceSession,
conditional: expCond.conditional,
});
}
if (!trigger_obj.data.action) {
func.utils.debug_report(SESSION_ID, 'get_view_events_count', `Error initiating ${typeP} prog:${_ds.v.viewSourceDesc} reason: missing action`, 'E');
break;
}
if (!glb.REFERENCE_LESS_FUNCTIONS.includes(trigger_obj.data.action) && !trigger_obj.data.action) {
func.utils.debug_report(SESSION_ID, 'get_view_events_count', `Error initiating ${typeP} prog: ${_ds.v.viewSourceDesc} reason: missing reference`, 'E');
break;
}
if (trigger_obj.data.enabled) {
_ds.viewEventExec_arr[index].push({
eventInfo: trigger_obj,
eventId: event_obj.id,
triggerId: trigger_obj.id,
expression: expression,
});
// ret++;
}
}
}
return _ds.viewEventExec_arr[index].length;
};
func.datasource.execute_view_events = async function (SESSION_ID, dataSourceSession, typeP, eventIdP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
var i = -1;
var index = typeP;
if (eventIdP) index = typeP + '_' + eventIdP;
var arr = _ds.viewEventExec_arr[index];
if (xu_isEmpty(arr)) return;
for await (const val of arr) {
if (!glb.IS_WORKER || !func.utils.is_onscreen_event(val.eventInfo.data.action) || (glb.IS_WORKER && _ds.v.run_at === 'server' && !func.utils.is_onscreen_event(val.eventInfo.data.action))) {
// val.done = true;
var cond = true;
if (val.expression) {
var expCond = await func.expression.get(SESSION_ID, val.expression, dataSourceSession, 'condition', args.rowIdP, null, null, null, null, val.eventInfo); // execute expression
cond = expCond.result;
}
if (cond) {
var elem_params = undefined;
if (!glb.IS_WORKER) {
const container_meta = func.runtime.ui.get_meta_by_element_id(_ds.containerId);
elem_params = container_meta?.params;
}
const ret = await func.events.execute(
SESSION_ID,
null,
val.triggerId,
val.eventInfo.data.trigger,
val.eventInfo.data.action,
val.eventInfo.data.name,
null,
null,
null,
null,
val.eventInfo.data.action,
null,
dataSourceSession,
val.eventId,
_ds.tree_obj.menuType + ' event',
true,
null,
null,
args.jobNoP,
elem_params,
null,
val.eventInfo,
);
}
continue;
}
// on screen event
if (typeP == 'before_record' || typeP == 'after_record' || typeP == 'on_load' || typeP == 'on_exit') {
_ds.v.onscreen_events_active = {
i: i,
type: typeP,
};
var parent_ds_chain = func.datasource.get_parent_ds_chain(SESSION_ID, dataSourceSession);
var obj = {
ds_obj: func.utils.clean_returned_datasource(SESSION_ID, dataSourceSession),
dsSessionP: dataSourceSession,
};
obj.ds_obj.parent_ds_chain = parent_ds_chain;
}
}
};
func.datasource.get_parent_ds_chain = function (SESSION_ID, dataSourceSession) {
var arr = [];
var drill = function (ds) {
if (SESSION_OBJ[SESSION_ID].DS_GLB[ds]) {
if (typeof SESSION_OBJ[SESSION_ID].DS_GLB[ds].parentDataSourceNo !== 'undefined') {
arr.push(SESSION_OBJ[SESSION_ID].DS_GLB[ds].parentDataSourceNo);
drill(SESSION_OBJ[SESSION_ID].DS_GLB[ds].parentDataSourceNo);
}
}
};
drill(dataSourceSession);
return arr;
};
func.datasource.execute_onscreen_view_events = async function (SESSION_ID, dataSourceSession, sourceP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
var i = _ds.v.onscreen_events_active.i;
var type = _ds.v.onscreen_events_active.type;
var evnt;
if (_ds.viewEventExec_arr?.[type]?.[i]) {
evnt = _ds.viewEventExec_arr[type][i];
evnt.done = true;
var cond = true;
if (evnt.expression) {
var expCond = await func.expression.get(SESSION_ID, evnt.expression, dataSourceSession, 'condition', args.rowIdP); // execute expression
cond = expCond.result;
}
if (cond) {
let ret = await func.events.execute(
SESSION_ID,
null,
evnt.triggerId,
evnt.eventInfo.data.trigger,
evnt.eventInfo.data.action,
evnt.eventInfo.data.name, //eventInfo[5],
null,
null,
null,
null,
evnt.eventInfo.data.name, //eventInfo[4],
null,
dataSourceSession,
null,
sourceP + ' event',
true,
null,
null,
args.jobNoP,
);
}
} else console.error('*execute_onscreen_view_events error');
};
func.datasource.get_event_interval_arr = function (SESSION_ID, dataSourceSession, typeP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
// var v = _ds.v;
var arr = [];
var ret = 0;
if (!_ds.v.progEvents) return 0;
for (let val of _ds.v.progEvents) {
if (val.data.type !== typeP) continue;
arr.push([val.id, val.data.properties, val.data.condition]);
}
return arr;
};
func.datasource.clean_all = function (SESSION_ID, dsP) {
var arr = [dsP];
var get_child_ds = function (ds) {
var arr = [];
for (const [key, val] of Object.entries(SESSION_OBJ[SESSION_ID].DS_GLB)) {
if (val.parentDataSourceNo == ds) {
arr.push(key);
arr = arr.concat(get_child_ds(key));
}
}
return arr;
};
arr = arr.concat(get_child_ds(dsP));
for (let val of arr) {
func.datasource.del(SESSION_ID, val);
}
};
func.datasource.clean = function (SESSION_ID, screenIdP) {
var arr = [];
for (const [key, val] of Object.entries(SESSION_OBJ[SESSION_ID].DS_GLB)) {
try {
const screen_parent_id = val.screenId && func.runtime?.ui?.get_parent_element_id
? func.runtime.ui.get_parent_element_id(val.screenId)
: null;
if (
Number(key) > 0 &&
(val.screenId === screenIdP ||
val.rootScreenId === screenIdP ||
screen_parent_id === screenIdP ||
(val && val.parentDataSourceNo && arr.includes(val.parentDataSourceNo.toString())))
) {
arr.push(key);
if (val.screenId && func.UI?.utils?.screen_blocker) func.UI.utils.screen_blocker(false, val.screenId);
}
} catch (err) {
console.warn('func.datasource.clean failed');
func.datasource.reset_jobs(SESSION_ID, key, 'datasource.clean', err);
}
}
for (let val of arr) {
func.datasource.del(SESSION_ID, val);
}
};
func.datasource.del = function (SESSION_ID, dsP) {
if ((SESSION_OBJ[SESSION_ID].DS_GLB[dsP] && SESSION_OBJ[SESSION_ID].DS_GLB[dsP].keep_alive) || dsP == 0) return;
if (DATASOURCE_INTERVALS[SESSION_ID] && DATASOURCE_INTERVALS[SESSION_ID][dsP]) {
DATASOURCE_INTERVALS[SESSION_ID][dsP].clear();
}
const perform_delete = async function () {
var response = {
success: function (jsonP, ajaxP) {},
error: function (status) {
console.error('error datasource:' + status);
},
fail: function (status) {
console.error('error datasource:' + status);
},
};
var data = {
session_id: SESSION_ID,
dssession: dsP,
};
if (!SESSION_OBJ[SESSION_ID].DS_GLB[dsP]) return;
if (!glb.IS_WORKER) {
let _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsP];
if (_ds.worker_id) {
// if (_ds.data_xuda) console.log(dsP);
if (RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED && (!_session.opt.app_computing_mode || _session.opt.app_computing_mode === 'server')) {
WEB_WORKER[SESSION_ID][_ds.worker_id].emit('message', {
service: 'close_websocket',
});
} else {
WEB_WORKER[SESSION_ID][_ds.worker_id].worker.terminate();
}
delete WEB_WORKER[SESSION_ID][_ds.worker_id];
} else {
const json = await func.index.call_worker(SESSION_ID, {
service: 'datasource_delete',
data: data,
id: _ds.worker_id,
});
response.success(json, true);
}
if (DS_UI_EVENTS_GLB) delete DS_UI_EVENTS_GLB[dsP];
}
delete SESSION_OBJ[SESSION_ID].DS_GLB[dsP];
};
var delete_pending_jobs = function () {
var arr = [];
for (const [key, val] of Object.entries(SESSION_OBJ[SESSION_ID].WORKER_OBJ.jobs)) {
if (val && val.dsSessionP == dsP) {
arr.push(key);
func.runtime.ui.clear_screen_blockers();
}
}
for (let val of arr.reverse()) {
SESSION_OBJ[SESSION_ID].WORKER_OBJ.jobs.splice(val, 1);
}
if (!SESSION_OBJ[SESSION_ID].WORKER_OBJ.jobs.length) SESSION_OBJ[SESSION_ID].WORKER_OBJ.stat = null;
};
if (!glb.IS_WORKER) {
if (SESSION_OBJ[SESSION_ID].DS_GLB[dsP]) {
delete SCREEN_BLOCKER_OBJ[SESSION_OBJ[SESSION_ID].DS_GLB[dsP].screenId + '_' + SESSION_OBJ[SESSION_ID].DS_GLB[dsP].callingScreenId];
// Refresh settled for this screen: once fully unblocked, reconcile
// teleports (drop orphans / mirror host visibility) on the next microtask
// (after this datasource completion unwinds) so there is no 1s lag on
// panel open/close. Gated so it is a no-op when there are no teleports.
if (func.UI?.reconcile_teleports) {
Promise.resolve().then(function () {
if (xu_isEmpty(SCREEN_BLOCKER_OBJ)) {
try {
func.UI.reconcile_teleports();
} catch (e) {}
}
});
}
delete_pending_jobs();
if (glb.new_xu_render) {
for (const [ui_cache_key, ui_cache_val] of Object.entries(UI_WORKER_OBJ.xu_render_cache)) {
if (ui_cache_val.paramsP.dsSessionP === dsP) {
delete UI_WORKER_OBJ.xu_render_cache[ui_cache_key];
}
}
}
}
const _nav_node = func.runtime.ui.get_first_node(SESSION_OBJ[SESSION_ID].root_element)?.querySelector?.('xu-nav');
if (_nav_node) {
var ds_obj = func.runtime.ui.get_data(_nav_node)?.xuData?.nav_params;
if (ds_obj) {
delete ds_obj[dsP];
}
}
}
perform_delete();
};
func.datasource.update = async function (SESSION_ID, datasource_changes, update_local_scope_only, avoid_xu_for_refresh, trigger) {
return new Promise(async (resolve, reject) => {
var _session = SESSION_OBJ[SESSION_ID];
if (glb.XU_PERF) {
// any state mutation invalidates memoized drive-ref clean verdicts and
// the per-epoch expression slot clones
func.utils.drive_ref_clean_cache = new WeakSet();
func.expression._slot_clone_cache = new WeakMap();
}
const refresh_control = avoid_xu_for_refresh && typeof avoid_xu_for_refresh === 'object' ? avoid_xu_for_refresh : {};
const avoid_refresh = avoid_xu_for_refresh === true || refresh_control.avoid_refresh === true;
const refresh_attributes_when_avoiding = refresh_control.refresh_attributes === true;
const skip_attribute_refresh = avoid_refresh && !refresh_attributes_when_avoiding;
const skip_screen_refresh = avoid_refresh || refresh_control.avoid_screen_refresh === true;
const defer_screen_refresh = !skip_screen_refresh && refresh_control.defer_refresh === true;
if (_session.IS_API || typeof IS_MASTER_WEBSOCKET !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
update_local_scope_only = true;
}
if (typeof glb.GLOBAL_VARS === 'undefined') {
glb.GLOBAL_VARS = (await func.common.get_module(SESSION_ID, 'xuda-system-globals-module.mjs')).system_globals;
}
const set_fieldComputed_dependencies = async function (dsNo, field_id, parent_ds) {
// iterate child ds
for (const [dsSession, _ds] of Object.entries(_session.DS_GLB)) {
if (parent_ds !== null) {
if (_ds.parentDataSourceNo != parent_ds) continue;
} else {
if (dsSession != dsNo) continue;
}
let tree_ret = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id);
if (tree_ret.menuType === 'component' || tree_ret.menuType === 'globals') {
// check if field has fieldComputed property
const _progFields = await func.datasource.get_progFields(SESSION_ID, dsSession);
// find if field is computed
let fieldComputed_propExpressions, fieldComputed_id;
for await (const val of _progFields) {
const fieldId = val.data.field_id;
// if (fieldId !== field_id) continue
if (val.data.type !== 'virtual' || !val.props.fieldComputed) continue;
const _propExpressions = val.props?.propExpressions?.fieldValue;
if (_propExpressions && JSON.stringify(_propExpressions).includes(field_id)) {
fieldComputed_propExpressions = _propExpressions;
fieldComputed_id = fieldId;
}
}
if (!fieldComputed_id) return;
// iterate ds rows
for (const row of _ds.data_feed?.rows || []) {
// iterate row fields
for (const [key, val] of Object.entries(row)) {
if (key !== fieldComputed_id) continue;
try {
let ret = await func.expression.get(SESSION_ID, fieldComputed_propExpressions, dsNo, 'update', row._ROWID);
const row_idx = func.common.find_ROWID_idx(_ds, row._ROWID);
if (_ds.data_feed.rows[row_idx][fieldComputed_id] !== ret.result) {
_ds.data_feed.rows[row_idx][fieldComputed_id] = ret.result;
if (!fields_changed.includes(fieldComputed_id)) {
fields_changed.push(fieldComputed_id);
}
if (!datasource_changed.includes(dsSession)) {
datasource_changed.push(dsSession);
}
}
} catch (err) {
console.error(err);
}
}
}
}
await set_fieldComputed_dependencies(dsNo, field_id, dsSession);
}
};
var fields_changed = [];
var datasource_changed = [];
let client_datasource_changes = {};
let server_datasource_changes = {};
const mark_field_changed = async function (dataSource, field_id) {
if (!fields_changed.includes(field_id)) {
fields_changed.push(field_id);
// Refresh dependent in-parameters that reference this field.
for (const [_dsSession, _ds] of Object.entries(_session.DS_GLB)) {
if (_ds.args.parameters_raw_obj) {
for (const [key, exp] of Object.entries(_ds.args.parameters_raw_obj)) {
if (exp.includes(field_id)) {
let ret = await func.expression.get(SESSION_ID, exp, _dsSession, 'parameters');
_ds.in_parameters[key].value = ret.result;
}
}
}
}
}
if (!datasource_changed.includes(dataSource)) {
datasource_changed.push(dataSource);
}
};
const queue_remote_change = async function (dataSource, record_id, field_id, value, _ds) {
if (update_local_scope_only) {
return;
}
let tree_ret = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id);
if (glb.IS_WORKER) {
if (tree_ret.menuType === 'globals' || tree_ret.menuType === 'component') {
const _progFields = await func.datasource.get_progFields(SESSION_ID, dataSource);
let view_field_obj = func.common.find_item_by_key(_progFields, 'field_id', field_id);
if (!view_field_obj?.data?.serverField && record_id !== 'data_system') {
if (!client_datasource_changes[dataSource]) {
client_datasource_changes[dataSource] = {};
}
if (!client_datasource_changes[dataSource][record_id]) {
client_datasource_changes[dataSource][record_id] = {};
}
client_datasource_changes[dataSource][record_id][field_id] = value;
}
}
} else {
if ((tree_ret.menuType === 'component' && _ds._run_at !== 'client') || tree_ret.menuType === 'globals') {
if (!server_datasource_changes[dataSource]) {
server_datasource_changes[dataSource] = {};
}
if (!server_datasource_changes[dataSource][record_id]) {
server_datasource_changes[dataSource][record_id] = {};
}
server_datasource_changes[dataSource][record_id][field_id] = value;
}
}
};
const update_xu_ref = function (dataSource) {
let ret;
let _ds_0 = _session.DS_GLB[0];
for ([ref_name, val] of Object.entries(_ds_0.data_system['SYS_GLOBAL_OBJ_REFS'])) {
if (val?.ds?.dsSession == dataSource) {
ret = func.UI.update_xu_ref(SESSION_ID, dataSource, ref_name);
}
}
return ret;
};
const mark_xu_refs_changed = function (dataSource) {
// Pushing the synthetic 'SYS_GLOBAL_OBJ_REFS' pseudo-field into fields_changed makes
// refresh_screen re-evaluate panel/modal visibility conditions that depend on @refs. This
// should only happen when update_xu_ref reports a real ref/data change.
if (!fields_changed.includes('SYS_GLOBAL_OBJ_REFS')) fields_changed.push('SYS_GLOBAL_OBJ_REFS');
};
// --- Watch fields (snapshot) -------------------------------------------------
// Before applying any change, snapshot the current value of every datasource's
// declared progDataSource.dataSourceWatchFields (Studio "Watch" popover). A watch
// field can be an in-parameter (e.g. pt_in) whose value is derived from another
// field, so we cannot detect it by matching fields_changed names — we compare its
// value before/after (see the fire-lifecycle block after the loop below).
const get_watch_field_value = function (watch_ds, watch_field_id) {
if (watch_ds?.in_parameters?.[watch_field_id] && typeof watch_ds.in_parameters[watch_field_id].value !== 'undefined') {
return watch_ds.in_parameters[watch_field_id].value;
}
const watch_rows = watch_ds?.data_feed?.rows || [];
const watch_row = watch_rows.find(function (row) { return row && row._ROWID === watch_ds.currentRecordId; }) || watch_rows[0] || {};
return watch_row[watch_field_id];
};
const watch_field_snapshot = {};
if (!glb.IS_WORKER) {
for (const [watch_dsSession, watch_ds] of Object.entries(_session.DS_GLB)) {
const watch_fields = watch_ds?.progDataSource?.dataSourceWatchFields;
if (!Array.isArray(watch_fields) || !watch_fields.length) {
continue;
}
watch_field_snapshot[watch_dsSession] = {};
for (let watch_index = 0; watch_index < watch_fields.length; watch_index++) {
const watch_field = watch_fields[watch_index];
const watch_field_id = typeof watch_field === 'string' ? watch_field.replace(/^@/, '') : (watch_field?.field_id ?? watch_field?.value ?? watch_field?.id);
if (watch_field_id) {
watch_field_snapshot[watch_dsSession][watch_field_id] = get_watch_field_value(watch_ds, watch_field_id);
}
}
}
}
// iterate changes datasource
for await (const [dataSource, row_data] of Object.entries(datasource_changes)) {
var _ds = _session.DS_GLB[dataSource];
if (!_ds) {
continue;
}
// iterate changes records
for (const [record_id, fields_data] of Object.entries(row_data)) {
// iterate changes fields
for (const [field_id, value] of Object.entries(fields_data)) {
// mechanism to make update directly on the datasource object
if (record_id === 'datasource_main') {
xu_set(_ds, field_id, value);
// stat/stat_ts/is_worker are datasource bookkeeping written on refresh lifecycle
// transitions. The value is still applied to _ds above; skip ref-change detection for
// these fields so lifecycle ticks do not schedule another screen refresh by themselves.
const _is_status_tick = field_id === 'stat' || field_id === 'stat_ts' || field_id === 'is_worker';
const ret = _is_status_tick ? false : update_xu_ref(dataSource);
if (ret) {
fields_changed.push(field_id);
datasource_changed.push(dataSource);
mark_xu_refs_changed(dataSource);
}
if (!glb.IS_WORKER && field_id === 'watcher') {
if (!server_datasource_changes[dataSource]) {
server_datasource_changes[dataSource] = {};
}
if (!server_datasource_changes[dataSource][record_id]) {
server_datasource_changes[dataSource][record_id] = {};
}
server_datasource_changes[dataSource][record_id][field_id] = value;
if (!update_local_scope_only) {
const ret = await func.index.call_worker(SESSION_ID, {
service: 'update_datasource_changes_from_client',
data: {
session_id: SESSION_ID,
datasource_changes: server_datasource_changes,
},
id: _ds.worker_id,
});
}
if (skip_screen_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh_skipped ' +
JSON.stringify({
reason: 'avoid_refresh',
phase: 'watcher',
dataSource,
field_id,
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
}),
);
} else if (defer_screen_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh_queued ' +
JSON.stringify({
phase: 'watcher',
dataSource,
field_id,
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
}),
);
func.runtime.ui.refresh_screen({
SESSION_ID,
fields_changed_arr: structuredClone(fields_changed),
datasource_changed: datasource_changed[0],
fields_changed_datasource: datasource_changed[0],
watcher: value,
})?.catch?.(function (error) {
console.error(error);
});
} else {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh ' +
JSON.stringify({
phase: 'watcher',
dataSource,
field_id,
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
}),
);
await func.runtime.ui.refresh_screen({
SESSION_ID,
fields_changed_arr: structuredClone(fields_changed),
datasource_changed: datasource_changed[0],
fields_changed_datasource: datasource_changed[0],
watcher: value,
});
}
}
continue;
}
if (typeof fields_data === 'object') {
if (glb.GLOBAL_VARS[field_id]) {
if (!_ds.data_system) {
_ds.data_system = {};
}
_ds.data_system[field_id] = value;
if (dataSource != 0 && _session.DS_GLB[0]) {
if (!_session.DS_GLB[0].data_system) {
_session.DS_GLB[0].data_system = {};
}
_session.DS_GLB[0].data_system[field_id] = value;
}
continue;
}
const dynamic_field = _ds?.dynamic_fields?.[field_id];
if (dynamic_field) {
if (!xu_isEqual(dynamic_field.value, value)) {
dynamic_field.value = value;
await set_fieldComputed_dependencies(dataSource, field_id, null);
if (update_xu_ref(dataSource)) {
mark_xu_refs_changed(dataSource);
}
await queue_remote_change(dataSource, record_id, field_id, value, _ds);
await mark_field_changed(dataSource, field_id);
}
continue;
}
try {
const row_idx = func.common.find_ROWID_idx(_ds, record_id);
// if (_ds.data_feed.rows[row_idx][field_id] !== value) {
if (!xu_isEqual(_ds.data_feed.rows[row_idx][field_id], value)) {
_ds.data_feed.rows[row_idx][field_id] = value;
await set_fieldComputed_dependencies(dataSource, field_id, null);
// search the field in refs
if (update_xu_ref(dataSource)) {
mark_xu_refs_changed(dataSource);
}
await queue_remote_change(dataSource, record_id, field_id, value, _ds);
await mark_field_changed(dataSource, field_id);
if (!_ds.data_feed.rows_changed) {
_ds.data_feed.rows_changed = [];
}
if (!_ds.data_feed.rows_changed.includes(record_id)) _ds.data_feed.rows_changed.push(record_id);
}
} catch (error) {
// normal
}
} else if (fields_data === 'set') {
_ds.currentRecordId = record_id;
}
}
}
}
// --- Watch fields (fire lifecycle) -------------------------------------------
// Re-fire the on_load/screen_ready lifecycle for any datasource whose watched VALUE
// changed vs the pre-change snapshot (captured above, before the change loop). We
// compare VALUES, not fields_changed names, because a watch field is typically an
// in-parameter (e.g. pt_in) whose value is DERIVED from another field (active_pt_id_v
// via xu-exp:pt_in) — so the changed field name never equals the watch name. on_load
// and screen_ready are gated to INITIAL load only (run_on_load_events /
// schedule_panel_on_load_events) to avoid render loops, so data a screen_ready derives
// (e.g. a virtual field) stays stale on refresh. A watch field is an explicit trigger,
// so re-running the lifecycle is intended. Coalesced (latest state wins) + throttled
// (<=15 fires/sec per datasource) so a mis-configured watch on a field the lifecycle
// itself writes is broken with a warning instead of looping forever.
if (!glb.IS_WORKER && !xu_isEmpty(watch_field_snapshot)) {
const watch_field_state = (_session.__watch_field_state = _session.__watch_field_state || { active: new Set(), pending: new Set(), fire_times: {} });
const fire_watch_field_lifecycle = function (watch_dsSession) {
if (watch_field_state.active.has(watch_dsSession)) {
watch_field_state.pending.add(watch_dsSession);
return;
}
const now = Date.now();
watch_field_state.fire_times[watch_dsSession] = (watch_field_state.fire_times[watch_dsSession] || []).filter(function (fired_at) {
return now - fired_at < 1000;
});
if (watch_field_state.fire_times[watch_dsSession].length >= 15) {
console.warn('[xuda-runtime] watch-field lifecycle throttled for datasource ' + watch_dsSession + ' — a watch field may be written by its own on_load/screen_ready (loop).');
return;
}
watch_field_state.fire_times[watch_dsSession].push(now);
watch_field_state.active.add(watch_dsSession);
setTimeout(async function () {
try {
for (const lifecycle_event of ['on_load', 'screen_ready']) {
if (await func.datasource.get_view_events_count(SESSION_ID, watch_dsSession, lifecycle_event)) {
await func.datasource.execute_view_events(SESSION_ID, watch_dsSession, lifecycle_event);
}
}
} catch (watch_error) {
console.error(watch_error);
} finally {
watch_field_state.active.delete(watch_dsSession);
if (watch_field_state.pending.has(watch_dsSession)) {
watch_field_state.pending.delete(watch_dsSession);
fire_watch_field_lifecycle(watch_dsSession);
}
}
}, 0);
};
for (const watch_dsSession of Object.keys(watch_field_snapshot)) {
const watch_ds = _session.DS_GLB[watch_dsSession];
if (!watch_ds) {
continue;
}
let watch_value_changed = false;
const watch_field_ids = Object.keys(watch_field_snapshot[watch_dsSession]);
for (let watch_index = 0; watch_index < watch_field_ids.length; watch_index++) {
const watch_field_id = watch_field_ids[watch_index];
if (!xu_isEqual(watch_field_snapshot[watch_dsSession][watch_field_id], get_watch_field_value(watch_ds, watch_field_id))) {
watch_value_changed = true;
break;
}
}
if (watch_value_changed) {
fire_watch_field_lifecycle(watch_dsSession);
}
}
}
if (glb.IS_WORKER) {
if (!update_local_scope_only && !xu_isEmpty(client_datasource_changes)) {
func.utils.post_back_to_client(SESSION_ID, 'update_client_eventChangesResults_from_worker', _session.worker_id, client_datasource_changes);
}
} else {
if (!update_local_scope_only && !xu_isEmpty(server_datasource_changes)) {
const ret = await func.index.call_worker(SESSION_ID, {
service: 'update_datasource_changes_from_client',
data: {
session_id: SESSION_ID,
datasource_changes: server_datasource_changes,
},
id: _ds.worker_id,
});
}
///// REFRESH SCREEN
if (fields_changed.length) {
function findMin(arr) {
return Math.min(...arr.map(Number));
}
if (!skip_attribute_refresh) {
if (skip_screen_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_attributes_only ' +
JSON.stringify({
reason: 'avoid_screen_refresh',
phase: 'fields',
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
trigger: trigger || null,
}),
);
} else if (defer_screen_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh_queued ' +
JSON.stringify({
phase: 'fields',
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
trigger: trigger || null,
}),
);
} else {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh ' +
JSON.stringify({
phase: 'fields',
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
trigger: trigger || null,
}),
);
}
// await func.UI.screen.refresh_xu_attributes(SESSION_ID, _.cloneDeep(fields_changed), null, null, findMin(datasource_changed), avoid_xu_for_refresh, trigger);
await func.runtime.ui.refresh_xu_attributes({
SESSION_ID,
fields_arr: structuredClone(fields_changed),
jobNoP: null,
$elm_to_search: null,
dsSession_changed: findMin(datasource_changed),
avoid_xu_for_refresh: skip_screen_refresh,
trigger,
ignore_screen_blocker: true,
});
}
if (skip_screen_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh_skipped ' +
JSON.stringify({
reason: refresh_attributes_when_avoiding ? 'avoid_screen_refresh' : 'avoid_refresh',
phase: 'fields',
attributes_refreshed: !skip_attribute_refresh,
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
}),
);
} else if (defer_screen_refresh) {
func.runtime.ui.refresh_screen({
SESSION_ID,
fields_changed_arr: structuredClone(fields_changed),
datasource_changed: null,
fields_changed_datasource: datasource_changed[0],
})?.catch?.(function (error) {
console.error(error);
});
} else {
// await removed from the below function cause to dead lock Mar 3 25
await func.runtime.ui.refresh_screen({
SESSION_ID,
fields_changed_arr: structuredClone(fields_changed),
datasource_changed: null,
fields_changed_datasource: datasource_changed[0],
});
}
}
// ///// REFRESH PARAMETERS IN
// if (fields_changed.length) {
// for (const [_dsSession, _ds] of Object.entries(_session.DS_GLB)) {
// if (_ds.args.parameters_raw_obj) {
// for (const [key, val] of Object.entries(_ds.args.parameters_raw_obj)) {
// if (fields_changed.includes(val)) {
// let ret = await func.expression.get(SESSION_ID, '@' + val, _dsSession, 'parameters');
// _ds.in_parameters[val].value = ret.result;
// }
// }
// }
// }
// }
}
resolve();
});
};
func.datasource.callback = async function (SESSION_ID, dsSessionP, rowIdP, jobNoP, nodeIdP) {
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSessionP];
try {
const row_idx = func.common.find_ROWID_idx(_ds, 'dataset');
if (_ds.PARAM_OUT_INFO) {
// write log for out params
for (const [key, val] of Object.entries(_ds.PARAM_OUT_INFO)) {
if (typeof _ds?.data_feed?.rows?.[row_idx]?.[val.fieldId] === 'undefined') {
// func.utils.debug.log(SESSION_ID, key, val);
func.utils.alerts.invoke(SESSION_ID, 'system_msg', 'SYS_MSG_0310', val.fieldId, dsSessionP);
break;
}
val.result = _ds.data_feed.rows[row_idx][val.fieldId];
}
}
} catch (err) {
// console.error(err);
}
const datasetOutputField = _ds?.progDataSource?.datasetOutputField;
if (datasetOutputField) {
let ret_get_value = await func.datasource.get_value(SESSION_ID, datasetOutputField, _ds.dsSession);
if (ret_get_value.found) {
let datasource_changes = {};
if (!datasource_changes[ret_get_value.dsSessionP]) {
datasource_changes[ret_get_value.dsSessionP] = {};
}
if (!datasource_changes[ret_get_value.dsSessionP][ret_get_value.currentRecordId]) {
datasource_changes[ret_get_value.dsSessionP][ret_get_value.currentRecordId] = {};
datasource_changes[ret_get_value.dsSessionP][ret_get_value.currentRecordId][_ds?.progDataSource?.datasetOutputField] = _ds?.data_feed?.rows || [];
await func.datasource.update(SESSION_ID, datasource_changes);
}
}
}
func.utils.debug.log(SESSION_ID, nodeIdP, {
module: _ds.viewModule,
action: 'close',
source: _ds.viewSourceDesc,
type: 'adapter',
prog_id: _ds.prog_id,
dsSession: dsSessionP,
prop: _ds.log_prop + ' ' + 'adapter',
});
if (!glb.IS_WORKER) func.runtime.platform.set_cursor(_session.root_element, 'default');
if (_ds.prog_id) {
let _ds = _session.DS_GLB[dsSessionP];
func.utils.debug.watch(
SESSION_ID,
_ds.prog_id,
'program',
{
in_parameters: _ds.in_parameters,
out_parameters: _ds.out_parameters,
data_feed: _ds.data_feed,
},
_ds.tree_obj.menuType,
);
}
delete _ds.old_dataSource;
return {
SESSION_ID,
dsSessionP,
rowIdP,
jobNoP,
callingLogId: _ds.callingLogId,
calling_jobP: _ds.calling_jobP,
};
};
func.datasource.validate_viewRange = async function (SESSION_ID, viewRangeExpP, dsSessionP, rowIdP, sourceP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (viewRangeExpP && _ds) {
var ret = func.expression.remove_quotes(await func.expression.get(SESSION_ID, viewRangeExpP, dsSessionP, 'range', rowIdP));
ret.result = func.expression.remove_quotes(ret.result);
func.utils.debug.log(SESSION_ID, _ds.prog_id, {
module: _ds.viewModule,
action: 'range Exp',
prop: sourceP,
details: viewRangeExpP,
result: ret.result,
error: ret.error,
source: _ds.viewSourceDesc,
json: ret.explain,
fields: ret.fields,
dsSession: dsSessionP,
});
_ds.viewRangeExpResults = ret.fields;
if (glb.DEBUG_MODE) {
if (!_ds.debug) {
_ds.debug = {};
}
if (!_ds.debug.viewRangeExp) {
_ds.debug.viewRangeExp = [];
}
_ds.debug.viewRangeExp.push(ret);
}
return ret.result;
} else return false;
};
func.datasource.validate_viewLocate = async function (SESSION_ID, viewLocateExpP, dsSessionP, rowIdP, sourceP) {
if (viewLocateExpP && _ds) {
var ret = func.expression.remove_quotes(await func.expression.get(SESSION_ID, viewLocateExpP, dsSessionP, 'locate', rowIdP));
ret.result = func.expression.remove_quotes(ret.result);
func.utils.debug.log(SESSION_ID, _ds.prog_id, {
module: _ds.viewModule,
action: 'locate Exp',
prop: sourceP,
details: viewLocateExpP,
result: ret.result,
error: ret.error,
source: _ds.viewSourceDesc,
json: ret.explain,
fields: ret.fields,
dsSession: dsSessionP,
});
_ds.viewLocateExpResults = ret.fields;
return ret.result;
} else return false;
};
func.datasource.get_viewFields_for_update_function = function (SESSION_ID, calling_trigger_prop, na, dsSessionP) {
var viewFields = [];
var exp = calling_trigger_prop?.data?.name?.value;
if (!exp) {
return viewFields;
}
const trim_wrapping_braces = function (value) {
const trimmed = value.trim();
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
return trimmed.substring(1, trimmed.length - 1);
}
return trimmed;
};
const strip_wrapping_quotes = function (value) {
const trimmed = value.trim();
const first = trimmed.substring(0, 1);
const last = trimmed.substring(trimmed.length - 1);
if ((first === "'" || first === '"' || first === '`') && last === first) {
return trimmed.substring(1, trimmed.length - 1);
}
return trimmed;
};
const split_top_level = function (value) {
const parts = [];
let current = '';
let quote = null;
let escape = false;
let paren_depth = 0;
let bracket_depth = 0;
let brace_depth = 0;
for (let index = 0; index < value.length; index++) {
const char = value[index];
if (escape) {
current += char;
escape = false;
continue;
}
if (quote) {
current += char;
if (char === '\\') {
escape = true;
} else if (char === quote) {
quote = null;
}
continue;
}
if (char === "'" || char === '"' || char === '`') {
quote = char;
current += char;
continue;
}
if (char === '(') paren_depth++;
if (char === ')') paren_depth = Math.max(0, paren_depth - 1);
if (char === '[') bracket_depth++;
if (char === ']') bracket_depth = Math.max(0, bracket_depth - 1);
if (char === '{') brace_depth++;
if (char === '}') brace_depth = Math.max(0, brace_depth - 1);
if ((char === ',' || char === ';') && !paren_depth && !bracket_depth && !brace_depth) {
if (current.trim()) {
parts.push(current.trim());
}
current = '';
continue;
}
current += char;
}
if (current.trim()) {
parts.push(current.trim());
}
return parts;
};
const find_top_level_colon = function (value) {
let quote = null;
let escape = false;
let paren_depth = 0;
let bracket_depth = 0;
let brace_depth = 0;
for (let index = 0; index < value.length; index++) {
const char = value[index];
if (escape) {
escape = false;
continue;
}
if (quote) {
if (char === '\\') {
escape = true;
} else if (char === quote) {
quote = null;
}
continue;
}
if (char === "'" || char === '"' || char === '`') {
quote = char;
continue;
}
if (char === '(') paren_depth++;
if (char === ')') paren_depth = Math.max(0, paren_depth - 1);
if (char === '[') bracket_depth++;
if (char === ']') bracket_depth = Math.max(0, bracket_depth - 1);
if (char === '{') brace_depth++;
if (char === '}') brace_depth = Math.max(0, brace_depth - 1);
if (char === ':' && !paren_depth && !bracket_depth && !brace_depth) {
return index;
}
}
return -1;
};
exp = trim_wrapping_braces(exp.replace(/\n/gi, ''));
const exp_arr = split_top_level(exp);
for (let index = 0; index < exp_arr.length; index++) {
const segment = exp_arr[index];
const pos = find_top_level_colon(segment);
if (pos === -1) {
continue;
}
let id = strip_wrapping_quotes(segment.substring(0, pos));
const val = segment.substring(pos + 1).trim();
if (id.substring(0, 1) === '@') {
id = id.substring(1);
}
if (!id || !val) {
continue;
}
viewFields.push({
id,
val,
});
}
return viewFields;
};
func.datasource.get_value = async function (SESSION_ID, fieldIdP, dsSessionP, rowIdP, org_dsSessionP) {
const normalize_field_id = function (field_id) {
if (typeof field_id === 'string') {
return field_id;
}
if (typeof field_id?.field_id === 'string') {
return field_id.field_id;
}
if (typeof field_id?.id === 'string') {
return field_id.id;
}
if (typeof field_id === 'number' || typeof field_id === 'boolean' || typeof field_id === 'bigint') {
return field_id.toString();
}
const coerced = field_id?.toString?.();
if (typeof coerced === 'string' && coerced && coerced !== '[object Object]') {
return coerced;
}
return null;
};
const return_missing_value = function (field_id, currentRecordId = null) {
return {
ret: {
value: undefined,
type: 'string',
prop: null,
},
dsSessionP,
fieldIdP: field_id,
currentRecordId,
found: false,
};
};
let row_lookup_issue = null;
const remember_row_lookup_issue = function (err, field_id, record_id) {
if (row_lookup_issue) return;
row_lookup_issue = {
err,
details: {
dsSessionP,
field_id,
record_id,
},
};
};
const report_row_lookup_issue = async function (lookup_ret) {
if (row_lookup_issue && !lookup_ret?.found) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_DSC_060',
source: 'Datasource get value',
message: 'Datasource row lookup failed',
type: 'W',
err: row_lookup_issue.err,
details: row_lookup_issue.details,
skip_log: false,
});
}
return lookup_ret;
};
const return_value = async (field_id, value) => {
const _progFields = await func.datasource.get_progFields(SESSION_ID, dsSessionP);
let view_field_obj = func.common.find_item_by_key(_progFields, 'field_id', field_id);
var fieldType = view_field_obj?.props?.fieldType || 'string';
var fieldProp = view_field_obj?.props;
let table_field_obj;
if (view_field_obj?.data?.type === 'table') {
if (!_ds._dataSourceTableId) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `Table type defined without dataSourceTableId deceleration`, 'E');
}
let table_obj = await func.utils.FILES_OBJ.get(SESSION_ID, _ds._dataSourceTableId);
if (!table_obj) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `dataSourceTableId reference error: ` + _ds._dataSourceTableId, 'E');
}
table_field_obj = func.common.find_item_by_key(table_obj.tableFields, 'field_id', field_id);
fieldType = table_field_obj.props?.fieldType;
fieldProp = table_field_obj.props;
}
let ret = {
value: await func.common.get_cast_val(SESSION_ID, `datasource get value ${_ds.tree_obj.menuName}`, fieldIdP, fieldType, value, null),
type: fieldType,
prop: fieldProp,
};
if (ret.value && typeof ret.value === 'string' && ret.type !== 'exp') {
if (/"/.test(ret.value) && ret.value.indexOf('\\') === -1) ret.value = ret.value.replace(/"/g, '"');
}
return {
ret,
dsSessionP,
fieldIdP: field_id,
currentRecordId: _ds.currentRecordId,
found: typeof value !== 'undefined',
};
};
const return_dynamic_value = async (field_id, value) => {
let view_field_obj = _ds.dynamic_fields[field_id];
var fieldType = view_field_obj?.props?.fieldType || 'string';
var fieldProp = view_field_obj?.props;
let ret = {
value: view_field_obj.value,
type: fieldType,
prop: fieldProp,
};
if (ret.value && typeof ret.value === 'string' && ret.type !== 'exp') {
if (/"/.test(ret.value) && ret.value.indexOf('\\') === -1) ret.value = ret.value.replace(/"/g, '"');
}
return {
ret,
dsSessionP,
fieldIdP: field_id,
currentRecordId: _ds.currentRecordId,
found: typeof value !== 'undefined',
};
};
const return_value_parameters = async (field_id, value) => {
let ret = {
value: await func.common.get_cast_val(SESSION_ID, 'datasource get value', fieldIdP, value.type, value.value, null),
type: value.type,
prop: null,
};
if (ret.value && typeof ret.value === 'string' && ret.type !== 'exp') {
if (!ret.value.includes('<svg xmlns=') && /"/.test(ret.value) && ret.value.indexOf('\\') === -1) ret.value = ret.value.replace(/"/g, '"');
// if (/"/.test(ret.value) && ret.value.indexOf("\\") === -1)
// ret.value = ret.value.replace(/"/g, '\\"');
}
return {
ret,
dsSessionP,
fieldIdP: field_id,
currentRecordId: _ds.currentRecordId,
found: typeof value !== 'undefined',
};
};
const return_value_system = async (field_id, value) => {
let fieldType = glb.GLOBAL_VARS[field_id].type;
let fieldProp = null;
let ret = {
value: await func.common.get_cast_val(SESSION_ID, `datasource get value ${_ds.tree_obj.menuName}`, field_id, fieldType, value, null),
type: fieldType,
prop: fieldProp,
};
return {
ret,
dsSessionP,
fieldIdP: field_id,
currentRecordId: _ds.currentRecordId,
found: typeof value !== 'undefined',
};
};
const search_in_parameters = async (field_id) => {
if (typeof _ds?.in_parameters?.[field_id]?.value !== 'undefined') {
let ret = await return_value_parameters(field_id, _ds.in_parameters[field_id]);
return ret;
}
if (typeof _ds.parentDataSourceNo !== 'undefined') {
var org_dsSession = org_dsSessionP;
if (!org_dsSessionP) org_dsSession = dsSessionP;
let parent_record_id = recordId;
if (parent_record_id && parent_record_id !== 'newRecord') {
const parent_ds = SESSION_OBJ[SESSION_ID].DS_GLB[_ds.parentDataSourceNo];
const parent_has_record = !!parent_ds?.data_feed?.rows?.some((row) => row?._ROWID === parent_record_id);
if (parent_has_record) {
org_dsSession = null;
} else {
parent_record_id = null;
}
}
return await func.datasource.get_value(SESSION_ID, fieldIdP, _ds.parentDataSourceNo, parent_record_id, org_dsSession);
}
return await return_value(field_id);
};
if (typeof glb.GLOBAL_VARS === 'undefined') {
glb.GLOBAL_VARS = (await func.common.get_module(SESSION_ID, 'xuda-system-globals-module.mjs')).system_globals;
}
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (!_ds) {
if (dsSessionP > 0) {
// let prev_ds = dsSessionP - 1;
return await func.datasource.get_value(SESSION_ID, fieldIdP, dsSessionP - 1, rowIdP, org_dsSessionP);
}
const normalized_missing_field = normalize_field_id(fieldIdP);
if (normalized_missing_field === null) {
return return_missing_value(fieldIdP);
}
fieldIdP = normalized_missing_field;
return await return_value(fieldIdP);
} //console.error("error: datasource not exist: " + dsSessionP);
const normalized_field_id = normalize_field_id(fieldIdP);
if (normalized_field_id === null) {
func.utils.debug_report(SESSION_ID, 'Datasource get value', `Invalid field id type: ${typeof fieldIdP}`, 'W');
return return_missing_value(fieldIdP, _ds.currentRecordId);
}
fieldIdP = normalized_field_id;
let recordId = rowIdP;
if (!recordId) {
recordId = _ds.currentRecordId;
}
if (glb.GLOBAL_VARS[fieldIdP]) {
if (!_ds.data_system) {
_ds.data_system = {};
}
if (dsSessionP > 0) {
_ds.data_system['SYS_STR_ACTIVE_ROW_ID'] = _ds.currentRecordId;
_ds.data_system['SYS_STR_PROG_DS_SESSION'] = dsSessionP;
}
// set system time
if (glb.SYS_DATE_ARR.includes(fieldIdP)) {
var _ds_0 = SESSION_OBJ[SESSION_ID].DS_GLB[0];
if (_ds_0) {
if (!_ds_0.data_system) {
_ds_0.data_system = {};
}
const ts = await func.utils.get_dateTime(SESSION_ID, 'SYS_DATE_VALUE');
for (const val of glb.SYS_DATE_ARR) {
_ds_0.data_system[val] = await func.utils.get_dateTime(SESSION_ID, val, ts);
}
}
}
// await func.datasource.set_system_vars(SESSION_ID, dsSessionP);
if (typeof _ds?.data_system?.[fieldIdP] !== 'undefined') {
return await return_value_system(fieldIdP, _ds?.data_system?.[fieldIdP]);
}
return await search_in_parameters(fieldIdP);
}
// if (glb.PROTECTED_VARS[fieldIdP]) {
// switch (fieldIdP) {
// case "_ROW_NO": {
// let row_no = Object.keys(_ds?.data_feed?.rows).indexOf(recordId) || 0;
// return await return_value(fieldIdP, row_no);
// }
// default:
// break;
// }
// }
if (!_ds.data_feed) {
return await search_in_parameters(fieldIdP);
}
var _field_id = fieldIdP;
if (fieldIdP.substr(0, 1) === '_') {
if (_ds.alias) _field_id = _ds.alias[fieldIdP];
}
if (typeof _ds?.dynamic_fields?.[_field_id] !== 'undefined') {
return await return_dynamic_value(_field_id, _ds.dynamic_fields[_field_id]);
}
if (!org_dsSessionP && recordId) {
try {
const row_idx = func.common.find_ROWID_idx(_ds, recordId);
if (typeof _ds.data_feed?.rows?.[row_idx]?.[_field_id] !== 'undefined') {
if (Object.keys(_ds.data_feed?.rows?.[row_idx] || {})?.includes(_field_id)) {
return await return_value(_field_id, _ds.data_feed.rows[row_idx][_field_id]);
}
if (Object.keys(_ds?.dynamic_fields || {})?.includes(_field_id)) {
return await return_dynamic_value(_field_id, _ds.dynamic_fields[_field_id]);
}
}
} catch (err) {
remember_row_lookup_issue(err, _field_id, recordId);
}
}
if (_ds.currentRecordId) {
try {
const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
if (typeof _ds.data_feed?.rows?.[row_idx]?.[_field_id] !== 'undefined') {
return await return_value(_field_id, _ds.data_feed.rows[row_idx][_field_id]);
}
} catch (error) {
remember_row_lookup_issue(error, _field_id, _ds.currentRecordId);
}
}
return await report_row_lookup_issue(await search_in_parameters(fieldIdP));
};
func.datasource.find_event_dataSource = async function (SESSION_ID, eventIdP, dsSessionP) {
const _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
var ret;
//============================
// Search in given datasource
//============================
if (_ds?.prog_id) {
let view_ret = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
if (view_ret?.progEvents && func.common.find_item_by_key(view_ret.progEvents, 'event_name', eventIdP)) {
ret = dsSessionP;
if (_ds.callingSource === 'system') ret = 0;
if (_ds.callingSource === 'program' || !_ds.callingSource) ret = dsSessionP;
// >>> found value
return ret;
}
}
if (!ret && dsSessionP !== 0) {
//=======================================
// continue Search in parent datasource
//=======================================
if (_ds && _ds.parentDataSourceNo && Number(_ds.parentDataSourceNo) > 0 && Number(_ds.parentDataSourceNo) < dsSessionP) {
return await func.datasource.find_event_dataSource(SESSION_ID, eventIdP, _ds.parentDataSourceNo);
} else {
//========================================
// continue Search in global system
//=========================================
if (!ret) return await func.datasource.find_event_dataSource(SESSION_ID, eventIdP, 0);
}
}
};
func.datasource.reset_jobs = function (SESSION_ID, dsSessionP, sourceP, errP) {
for (const [key, val] of Object.entries(SESSION_OBJ[SESSION_ID].WORKER_OBJ.jobs)) {
if (val.dsSessionP === dsSessionP) {
func.events.delete_job(SESSION_ID, val.job_num);
break;
}
}
func.utils.debug_report(SESSION_ID, sourceP + 'Missing datasource: ' + dsSessionP, errP, 'W', null);
};
// func.datasource.set_system_vars = async function (SESSION_ID, dsSessionP) {
// var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
// if (!_ds) return;
// if (!_ds.data_system) {
// _ds.data_system = {};
// }
// if (dsSessionP > 0) {
// _ds.data_system["SYS_STR_ACTIVE_ROW_ID"] = _ds.currentRecordId;
// _ds.data_system["SYS_GLOBAL_STR_PROG_DS_SESSION"] = dsSessionP;
// }
// // _ds.data_system["SYS_OBJ_DS_INFO"] = {
// // rows: _ds.rows_found,
// // first_row_id: _ds.firstRecordId,
// // last_row_id: _ds.lastRecordId,
// // query_from_segments_json: _ds.filter_from,
// // query_to_segments_json: _ds.filter_from,
// // locate_query_from_segments_json: _ds.locate_from,
// // locate_query_to_segments_json: _ds.locate_to,
// // first_row_segments_json: _ds.firstRecordKey_object,
// // last_row_segments_json: _ds.lastRecordKey_object,
// // rowid: _ds.currentRecordId,
// // };
// // if (!_ds.data_system["SYS_OBJ_DS_DATA"] && dsSessionP == 0) {
// // _ds.data_system["SYS_OBJ_DS_DATA"] = {
// // value: _.cloneDeep(_ds.data_system),
// // };
// // }
// var _ds_0 = SESSION_OBJ[SESSION_ID].DS_GLB[0];
// if (!_ds_0) return;
// if (!_ds_0.data_system) {
// _ds_0.data_system = {};
// }
// const ts = await func.utils.get_dateTime(SESSION_ID, "SYS_DATE_VALUE");
// for (const val of glb.SYS_DATE_ARR) {
// _ds_0.data_system[val] = await func.utils.get_dateTime(SESSION_ID, val, ts);
// }
// };
func.datasource.get_currentRecordId = function (SESSION_ID, dsSessionP, from_datasourceP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (_ds._dataSourceTableId !== '') {
var firstRecordId = _ds.firstRecordId; // returns the value of the fist row
var currentRecordId = _ds.currentRecordId; // record id that exist on ds after entering the form
var locatedRecordId = _ds.locatedRecordId; // record id located by ds
if (!currentRecordId || from_datasourceP) {
// first time without locate
if (!locatedRecordId) currentRecordId = firstRecordId;
// set first row when no rec located
else currentRecordId = locatedRecordId; // set with located row
}
} else currentRecordId = 'newRecord'; // create mode
return currentRecordId;
};
func.datasource.interval = function (session_id, dsSessionP, typeP) {
var SESSION_ID = session_id;
var interval = [];
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
// var ds = _ds;
// var v = ds.v;
// var arr = func.datasource.get_event_interval_arr(
// SESSION_ID,
// dsSessionP,
// typeP
// );
var arr = _ds[typeP];
var fx = {
init: async function () {
if (arr?.length) {
for (let val of arr) {
var event_id = val[0];
var interval_rate = val[1];
var condition = val[2];
interval.push(
setInterval(async function () {
if (!SESSION_OBJ[SESSION_ID]) return;
var event_count = await func.datasource.get_view_events_count(SESSION_ID, dsSessionP, typeP, event_id);
if (!event_count) {
fx.clear();
return;
}
var event_condition = await func.expression.get(SESSION_ID, condition, dsSessionP, 'condition'); // execute condition;
if (condition && !event_condition.result) return;
const e = await func.datasource.execute_view_events(SESSION_ID, dsSessionP, typeP, event_id);
// for (const [key, val] of Object.entries(e)) {
// val.done = false;
// }
}, Number(interval_rate) * 1000),
);
}
} else {
await fx.clear();
return;
}
},
clear: function () {
if (DATASOURCE_INTERVALS[session_id]) delete DATASOURCE_INTERVALS[session_id][dsSessionP];
for (const [key, val] of Object.entries(interval)) {
clearInterval(val);
}
},
};
return fx;
};
// func.datasource.server_cron = function (session_id) {
// var SESSION_ID = session_id;
// var jobs = [];
// var arr = func.datasource.get_event_interval_arr(SESSION_ID, 0, 'server_cron');
// const schedule = require('node-schedule');
// if (!arr.length) {
// return jobs;
// }
// for (let val of arr) {
// var event_id = val[0];
// var cron_prop = val[1];
// var condition = val[2];
// jobs.push(
// schedule.scheduleJob(cron_prop, async function () {
// if (!SESSION_OBJ[SESSION_ID]) return;
// var event_count = await func.datasource.get_view_events_count(SESSION_ID, 0, 'server_cron', event_id);
// if (!event_count) {
// fx.clear();
// return;
// }
// var event_condition = await func.expression.get(SESSION_ID, condition, 0, 'condition'); // execute condition;
// if (condition && !event_condition.result) return;
// if (event_count) {
// const e = await func.datasource.execute_view_events(SESSION_ID, 0, 'server_cron', event_id);
// // for (const [key, val] of Object.entries(e)) {
// // val.done = false;
// // }
// }
// }),
// );
// }
// return jobs;
// };
func.datasource.get_viewLoops = async function (SESSION_ID, dataSourceSession, data, batch_source, default_limit) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
// var v = _ds.v;
var ret = default_limit;
if (batch_source === 'db_data') ret = data.rows.length;
if (batch_source === 'array' || batch_source === 'csv') ret = data.length;
if (batch_source === 'json') ret = Object.keys(data).length;
if (_ds.progDataSource?.dataSourceLimit) {
if (batch_source !== 'no_data' && Number(_ds.progDataSource?.dataSourceLimit) < ret) {
ret = Number(_ds.progDataSource.dataSourceLimit);
}
if (!batch_source) ret = Number(_ds.progDataSource.dataSourceLimit);
}
if (prog_obj.progDataSource?.dataSourceLoopExp) {
var n = (await func.expression.get(SESSION_ID, _ds.v.viewLoopsExp, dataSourceSession, 'view_loop', args.rowIdP)).result;
if (batch_source !== 'no_data' && n < ret) ret = n;
if (!batch_source) ret = n;
}
return ret;
};
func.datasource.set_VIEW_data = async function (SESSION_ID, args, _ds) {
_ds.v = {
viewFieldsProp: {},
segFrom: [],
segTo: [],
segLocateFrom: [],
segLocateTo: [],
viewModule: 'adapter',
};
_ds.viewEventExec_arr = {};
var view = structuredClone(await func.utils.VIEWS_OBJ.get(SESSION_ID, args.prog_id));
// var view = klona.klona(await func.utils.VIEWS_OBJ.get(SESSION_ID, args.prog_id));
_ds.v.dataSourceSrcType = view.dataSourceSrcType;
if (view.progDataSource) _ds.progDataSource = view.progDataSource;
_ds.v.viewIndex = view?.progDataSource?.dataSourceIndexesObj;
let tree_ret = await func.utils.TREE_OBJ.get(SESSION_ID, args.prog_id);
_ds.v.viewSourceDesc = tree_ret.menuName;
if (!_ds.v.viewSourceDesc && tree_ret) {
_ds.v.viewSourceDesc = tree_ret.menuName;
}
if (glb.FUNCTION_NODES_ARR.includes(tree_ret.menuType)) {
_ds.v.viewModule = 'function';
}
_ds.v.viewSourceProp = tree_ret.menuType;
if (view.progEvents) _ds.v.progEvents = view.progEvents;
_ds._progDataSource_fields = [];
if (_ds.progDataSource) {
let ret = func.expression.parse(JSON.stringify(_ds.progDataSource));
_ds._progDataSource_fields = ret.map((e) => {
if (e.fieldId) return e.fieldId;
});
}
};
func.datasource.get_cast_val = async function (SESSION_ID, source, dsSession, valP, typeP, req, error) {
var prog_id, prog_name;
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSession];
prog_id = _ds.prog_id;
prog_name = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id).menuName;
const prog_info = prog_id ? ` (prog: ${prog_id} ${prog_name})` : '';
const report_conversion_error = function (res) {
var msg = `error converting from ${valP} to ${typeP}`;
if (error) {
return func.utils.debug_report(SESSION_ID, msg, '', 'W');
}
func.utils.debug_report(SESSION_ID, msg + ' ' + (source.charAt(0).toUpperCase() + source.slice(1).toLowerCase()) + prog_info, '', 'E');
};
const report_conversion_warn = function (res) {
// number/boolean/bigint -> string is lossless; skip the noise (it routes as an "Unhandled Runtime Error")
if (typeP === 'string' && (typeof valP === 'number' || typeof valP === 'boolean' || typeof valP === 'bigint')) return;
var msg = `type mismatch auto conversion from value ${valP} to ${typeP}`;
func.utils.debug_report(SESSION_ID, msg + ' ' + (source.charAt(0).toUpperCase() + source.slice(1).toLowerCase()) + prog_info, '', 'W');
};
// var ret = valP;
if (error) {
return report_conversion_error();
}
const module = await func.common.get_module(SESSION_ID, 'xuda-get-cast-util-module.mjs');
return module.cast(typeP, valP, report_conversion_error, report_conversion_warn);
};
func.datasource.get_field_init_triggers_to_run = function (SESSION_ID, dataSourceSession, pre_init_fieldsP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
if (!_ds) return;
return []; // inactive 021617
}; // inactive temporary, design to execute init workflow when querying large datasets
func.datasource.get_pre_init_fields = function (SESSION_ID, dsSessionP, viewRangeExpP, viewSortExpP, viewGroupByExpP, viewLocateExpP) {
var ret = [];
return; // inactive 021617
}; // temporary inactive, init preformed on the first round
func.datasource.add_dynamic_field_to_ds = function (SESSION_ID, dsSessionP, key, val) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (!_ds.dynamic_fields) {
_ds.dynamic_fields = {};
}
const toType = function (obj) {
return {}.toString
.call(obj)
.match(/\s([a-zA-Z]+)/)[1]
.toLowerCase();
};
_ds.dynamic_fields[key] = {
id: crypto.randomUUID(),
data: {
type: 'virtual',
field_id: key,
},
props: {
fieldType: typeof val !== 'undefined' ? toType(val) : 'string',
},
value: val,
};
};
func.datasource.get_progFields = async function (SESSION_ID, dsSessionP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
return _view_obj.progFields;
};
func.datasource.update_changes_for_out_parameter = async function (SESSION_ID, dsSessionP, calling_dsP, avoid_refreshP) {
let _session = SESSION_OBJ[SESSION_ID];
let _ds = _session.DS_GLB[dsSessionP];
const _calling_ds = _session.DS_GLB[calling_dsP];
const avoid_refresh = avoid_refreshP === true || avoid_refreshP?.avoid_refresh === true;
const refresh_options = avoid_refresh ? { avoid_refresh: true, refresh_attributes: true } : false;
const get_row_idx_safe = function (target_ds, row_id) {
if (!target_ds || !row_id) {
return null;
}
try {
return func.common.find_ROWID_idx(target_ds, row_id);
} catch (_error) {
return null;
}
};
if (!_ds?.PARAM_OUT_INFO || !calling_dsP || !_calling_ds) {
return;
}
let data = {};
for await (const [key, val] of Object.entries(_ds.PARAM_OUT_INFO)) {
if (val.prop === 'out') {
try {
const current_row_idx = get_row_idx_safe(_ds, _ds.currentRecordId);
const dataset_row_idx = get_row_idx_safe(_ds, 'dataset');
let result;
if (
current_row_idx !== null &&
typeof _ds?.data_feed?.rows?.[current_row_idx]?.[val.fieldId] !== 'undefined'
) {
result = _ds.data_feed.rows[current_row_idx][val.fieldId];
} else if (
dataset_row_idx !== null &&
typeof _ds?.data_feed?.rows?.[dataset_row_idx]?.[val.fieldId] !== 'undefined'
) {
result = _ds.data_feed.rows[dataset_row_idx][val.fieldId];
}
if (typeof result !== 'undefined') {
data[val.details] = result;
}
} catch (err) {
console.error(err);
}
}
}
if (!xu_isEmpty(data)) {
let datasource_changes = {
[calling_dsP]: { [_calling_ds.currentRecordId]: data },
};
try {
if (avoid_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] out_parameter_update ' +
JSON.stringify({
avoidRefresh: true,
source_ds: dsSessionP,
target_ds: calling_dsP,
fields: Object.keys(data),
}),
);
}
} catch (e) {}
await func.datasource.update(SESSION_ID, datasource_changes, null, refresh_options);
}
};
func.datasource.set_outputField = async function (SESSION_ID, dsSessionP, result, args, avoid_refreshP) {
var _session = SESSION_OBJ[SESSION_ID];
// let _ds = _session.DS_GLB[dsSessionP];
const avoid_refresh = avoid_refreshP === true || avoid_refreshP?.avoid_refresh === true;
const refresh_options = avoid_refresh ? { avoid_refresh: true, refresh_attributes: true } : false;
const output_field = await func.datasource.get_args_property_value(SESSION_ID, dsSessionP, args, 'outputField');
if (output_field) {
let datasource_changes = {};
let ret_get_value = await func.datasource.get_value(SESSION_ID, output_field, dsSessionP);
if (ret_get_value.found) {
let _ds = _session.DS_GLB[ret_get_value.dsSessionP];
if (!datasource_changes[_ds.dsSession]) {
datasource_changes[_ds.dsSession] = {};
}
if (!datasource_changes[_ds.dsSession][ret_get_value.currentRecordId]) {
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId] = {};
}
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId][output_field] = result;
try {
if (avoid_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] output_field_update ' +
JSON.stringify({
avoidRefresh: true,
target_ds: _ds.dsSession,
field: output_field,
}),
);
}
} catch (e) {}
await func.datasource.update(SESSION_ID, datasource_changes, null, refresh_options);
}
}
};
func.datasource.get_args_property_value = async function (SESSION_ID, dsSession, args, prop_name) {
// Headless executions (cron/emitter workers) have no calling trigger — _prop is
// undefined there and the unguarded read crashed every server-side get_data run.
let _prop = args?.calling_trigger_prop?.data?.name;
let _value = _prop?.[prop_name];
if (_prop?.[`xu-exp:${prop_name}`]) {
_value = (await func.expression.get(SESSION_ID, _prop[`xu-exp:${prop_name}`], dsSession, `${prop_name} expression`)).result;
}
return _value;
};
func.utils = {};
func.utils.debug = {};
func.utils.debug.watch = async function (SESSION_ID, key, type, info, result, condition, not_executed) {
if (!glb.DEBUG_MODE) return;
const debug_utils = await func.common.get_module(SESSION_ID, 'xuda-debug-utils-module.mjs');
debug_utils.watch(SESSION_ID, key, type, info, result, condition, not_executed);
};
func.utils.debug.log = async function (SESSION_ID, node_idP, jsonP) {
if (typeof IS_PROCESS_SERVER !== 'undefined') return;
if (!glb.DEBUG_MODE && !glb.TRACE_ON) return;
const debug_utils = await func.common.get_module(SESSION_ID, 'xuda-debug-utils-module.mjs');
debug_utils.log(SESSION_ID, node_idP, jsonP);
// console.error(jsonP);
};
func.utils.debug.write = async function (SESSION_ID, logP, callbackP) {
if (!glb.DEBUG_MODE) return;
const debug_utils = await func.common.get_module(SESSION_ID, 'xuda-debug-utils-module.mjs');
debug_utils.write(SESSION_ID, logP, callbackP);
};
func.utils.debug.read_command = async function (data) {
if (!glb.DEBUG_MODE) return;
const debug_utils = await func.common.get_module(SESSION_ID, 'xuda-debug-utils-module.mjs');
debug_utils.read_command(data);
};
func.utils.DOCS_OBJ = {};
func.utils.DOCS_OBJ.get = async function (SESSION_ID, idP) {
if (!idP || idP === '0') return;
const normalize_runtime_doc = function (doc) {
if (!doc || !func.runtime.program?.normalize_doc_for_runtime) {
return doc;
}
return func.runtime.program.normalize_doc_for_runtime(doc);
};
var _session = SESSION_OBJ[SESSION_ID];
const _app_id = _session.app_id;
if (!DOCS_OBJ[_app_id]) {
DOCS_OBJ[_app_id] = {};
}
if (DOCS_OBJ[_app_id][idP]) {
return DOCS_OBJ[_app_id][idP];
}
if (_session.project_data) {
if (idP === 'system') {
if (_session.project_data.globals) {
DOCS_OBJ[_app_id][idP] = _session.project_data.globals;
} else {
DOCS_OBJ[_app_id][idP] = {};
}
return DOCS_OBJ[_app_id][idP];
}
let val = _session.project_data?.programs?.[idP];
if (val) {
DOCS_OBJ[_app_id][idP] = normalize_runtime_doc(val);
return DOCS_OBJ[_app_id][idP];
}
}
if (typeof _session.SLIM_BUNDLE === 'undefined' || !_session.SLIM_BUNDLE) {
const module = await func.common.get_module(SESSION_ID, `xuda-progs-loader-module.mjs`);
if (idP !== 'system') {
DOCS_OBJ[_app_id][idP] = normalize_runtime_doc(await module.DOCS_OBJ_get(SESSION_ID, idP));
if (DOCS_OBJ[_app_id][idP] && xu_isEmpty(DOCS_OBJ[_app_id][idP])) {
await func.utils.remove_cached_objects(SESSION_ID);
delete DOCS_OBJ[_app_id][idP];
}
return DOCS_OBJ[_app_id][idP];
}
DOCS_OBJ[_app_id][idP] = await module.DOCS_OBJ_get(SESSION_ID, 'global_' + (APP_OBJ[_app_id].app_replicate || _app_id));
if (APP_OBJ[_app_id].app_imported_projects) {
for await (const imported_app_id of APP_OBJ[_app_id].app_imported_projects) {
var view_ret = await module.DOCS_OBJ_get(SESSION_ID, 'global_' + imported_app_id);
DOCS_OBJ[_app_id][idP] = Object.assign(DOCS_OBJ[_app_id][idP], view_ret);
}
}
return DOCS_OBJ[_app_id][idP];
}
console.error(`${idP} not found`);
};
func.utils.FILES_OBJ = {};
func.utils.FILES_OBJ.get = async function (SESSION_ID, idP) {
if (!idP) return;
return await func.utils.DOCS_OBJ.get(SESSION_ID, idP);
};
func.utils.VIEWS_OBJ = {};
func.utils.VIEWS_OBJ.get = async function (SESSION_ID, idP) {
if (!idP) return;
return await func.utils.DOCS_OBJ.get(SESSION_ID, idP);
};
func.utils.TREE_OBJ = {};
func.utils.TREE_OBJ.get = async function (SESSION_ID, idP) {
if (!idP) return;
var ret = await func.utils.DOCS_OBJ.get(SESSION_ID, idP);
if (ret?.properties) {
ret.properties.id = idP;
}
return ret.properties;
};
func.utils.get_dateTime = async function (SESSION_ID, typeP, dateP) {
// const getUTC = function (ts) {
// var date = new Date(ts);
// var utc_date = new Date(
// date.getUTCFullYear(),
// date.getUTCMonth(),
// date.getUTCDate(),
// date.getUTCHours(),
// date.getUTCMinutes(),
// date.getUTCSeconds(),
// date.getUTCMilliseconds()
// );
// return utc_date;
// };
const get_server_ts = async function () {
var _session = SESSION_OBJ[SESSION_ID];
const response = await fetch(`https://${_session.domain}/cpi/get_utc_ts`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
const json = await response.json();
return json.data;
// }
};
function getWeekNumber(d) {
// Copy date so don't modify original
d = new Date(+d);
d.setHours(0, 0, 0);
// Set to nearest Thursday: current date + 4 - current day number
// Make Sunday's day number 7
d.setDate(d.getDate() + 4 - (d.getDay() || 7));
// Get first day of year
var yearStart = new Date(d.getFullYear(), 0, 1);
// Calculate full weeks to nearest Thursday
var weekNo = Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
// Return array of year and week number
return weekNo;
}
var sysDate = new Date(dateP); // getUTC(dateP); //new Date(dateP);
if (!dateP) {
let ts = await get_server_ts();
sysDate = new Date(ts);
}
var day = String(sysDate.getDate()).padStart(2, '0');
var month = String(sysDate.getMonth() + 1).padStart(2, '0');
var year = sysDate.getFullYear();
var week = String(getWeekNumber(sysDate)).padStart(2, '0');
var hour = String(sysDate.getHours()).padStart(2, '0');
var minute = String(sysDate.getMinutes()).padStart(2, '0');
var second = String(sysDate.getSeconds()).padStart(2, '0');
if (typeP === 'SYS_DATE') return year + '-' + month + '-' + day;
if (typeP === 'SYS_DATE_TIME') return year + '-' + month + '-' + day + 'T' + hour + ':' + minute;
if (typeP === 'SYS_DATE_VALUE') return sysDate.valueOf();
if (typeP === 'SYS_DATE_WEEK_YEAR') return year + 'W' + week;
if (typeP === 'SYS_DATE_MONTH_YEAR') return year + '-' + month;
if (typeP === 'SYS_TIME') return hour + ':' + minute + ':' + second;
if (typeP === 'SYS_TIME_SHORT') return hour + ':' + minute;
};
func.utils.is_onscreen_event = function (functionP) {
const arr = ['invoke_action', 'cache_refresh', 'call_popover', 'call_modal', 'call_page', 'loader_on', 'loader_off', 'emit_event'];
return arr.includes(functionP);
};
func.utils.get_screen_obj = async function (SESSION_ID, id) {
const prog_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, id);
if (!prog_obj) return console.error('prog not found: ' + id);
// if (prog_obj.properties.menuType === 'screen' || prog_obj.properties.menuType === 'api' || glb.FUNCTION_NODES_ARR.includes(prog_obj.properties.menuType) || glb.MOBILE_ARR.includes(prog_obj.properties.menuType)) {
if (['component', ...glb.FUNCTION_NODES_ARR].includes(prog_obj.properties.menuType)) {
return prog_obj;
}
return;
};
func.utils.clean_returned_datasource = function (SESSION_ID, DS) {
const clean_object_functions = function (obj) {
for (const [key, val] of Object.entries(obj)) {
if (typeof val === 'function') {
delete obj[key];
}
}
};
var _session = SESSION_OBJ[SESSION_ID];
if (!_session.DS_GLB[DS]) return;
var obj = { ..._session.DS_GLB[DS] };
delete obj.screen_params;
// try {
// clean_object_functions(obj);
// obj = JSON.parse(JSON.stringify(obj, func.utils.clean_stringify_null, '\t'));
// } catch (e) {
// console.error(e);
// }
delete obj.pre_init_fields;
delete obj.oninit_triggers_to_run;
// delete obj.raw_data;
delete obj.debug;
const clean_empty_objects = function () {
for (const [key, val] of Object.entries(obj)) {
if (typeof val === 'object' && !Array.isArray(val) && xu_isEmpty(val)) {
delete obj[key];
}
}
for (const [key, val] of Object.entries(obj)) {
if (typeof val === 'object' && Array.isArray(val) && !val.length) {
delete obj[key];
}
}
};
delete obj.screenInfo;
delete obj.viewEventsProp;
delete obj.viewSourceDesc;
delete obj.viewSourceProp;
delete obj.v;
clean_empty_objects();
try {
clean_object_functions(obj);
obj = JSON.parse(JSON.stringify(obj, func.utils.clean_stringify_null, '\t'));
} catch (e) {
console.error(e);
}
// clean_dataset();
return obj;
};
func.utils.post_back_to_client = function (SESSION_ID, service, id, data) {
if (typeof IS_PROCESS_SERVER !== 'undefined') return;
worker_post_message({
fx_to_execute: service,
params: data,
session_id: SESSION_ID,
worker_id: id,
});
};
func.utils.job_worker = {};
func.utils.job_worker = function (session_id) {
var SESSION_ID = session_id;
var _session = SESSION_OBJ[SESSION_ID];
var is_progressScreen_on;
var is_not_responding;
var attempt = 0;
const lock = function (dsP) {
if (!_session.WORKER_OBJ.jobs[_session.WORKER_OBJ.stat] || _session.WORKER_OBJ.jobs[_session.WORKER_OBJ.stat].typeP === 'system_interval' || _session.WORKER_OBJ.jobs[_session.WORKER_OBJ.stat].typeP === 'system event') {
return;
}
if (glb.IS_WORKER) {
func.utils.post_back_to_client(SESSION_ID, 'screen_blocker_on', _session.worker_id, null);
} else {
func.UI.utils.screen_blocker(true, 'Worker', dsP);
}
};
const unlock = function () {
if (glb.IS_WORKER) {
} else {
func.UI.utils.screen_blocker(false, 'Worker');
}
};
const not_responding = function () {
is_not_responding = true;
func.UI.utils.progressScreen.hide('Working, Please wait..');
setTimeout(function () {
if (!is_not_responding) return;
reset();
}, 500);
};
const idle = function () {
if (is_progressScreen_on) {
setTimeout(function () {
if (!attempt && is_progressScreen_on) {
is_progressScreen_on = false;
is_not_responding = false;
func.UI.utils.progressScreen.hide('Working, Please wait..');
} else if (attempt > 300 && is_not_responding) {
is_not_responding = false;
busy();
}
}, 310);
} else {
if (!glb.IS_WORKER) {
// func.UI.screen.garbage_collector(SESSION_ID);
}
}
};
const busy = function () {
if (glb.IS_WORKER) return;
func.utils.debug_report(SESSION_ID, 'utils.worker.busy', 'worker processing more then 10 second', 'W', '', _session.WORKER_OBJ.jobs);
is_progressScreen_on = true;
};
const reset = function () {
func.utils.debug_report(SESSION_ID, 'utils.worker.reset', 'worker not responding', 'E', '', _session.WORKER_OBJ.jobs);
_session.WORKER_OBJ.jobs = [];
_session.WORKER_OBJ.stat = null;
func.runtime.ui.clear_screen_blockers();
};
return {
_interval: null,
_was_busy: null,
init: async function () {
var _this = this;
this._interval = setInterval(async function () {
var _session = SESSION_OBJ[SESSION_ID];
if (!_session?.WORKER_OBJ) return;
if (typeof _session.WORKER_OBJ.stat === 'undefined' || _session.WORKER_OBJ.stat === 'undefined' || _session.WORKER_OBJ.stat === null) {
// idle
// if (_this._was_busy) {
// if (glb.IS_WORKER) {
// func.utils.post_back_to_client(
// SESSION_ID,
// "worker_busy_off",
// _session.worker_id,
// null
// );
// } else {
// func.UI.utils.indicator.worker.normal();
// }
// _this._was_busy = false;
// }
unlock();
if (_session.WORKER_OBJ.jobs.length) {
for await (const [key, val] of Object.entries(_session.WORKER_OBJ.jobs)) {
if (val.stat) {
break;
}
// if (!_session.WORKER_OBJ.stat) {
if (!_session.WORKER_OBJ.jobs[Number(key)] || val.job_num === 9999999) {
continue;
}
if (val.dsSessionP && !_session.DS_GLB[val.dsSessionP]) {
func.events.delete_job(SESSION_ID, val.job_num);
break;
}
await func.events.execute(
SESSION_ID,
val.job_num,
val.eventIdP,
val.triggerP,
val.functionP,
val.refIdP,
val.containerP,
val.elementP,
val.rowP,
val.evt,
val.descP,
val.rootScreenIdP,
val.dsSessionP,
null,
val.typeP,
null,
val.event_propertiesP,
val.calling_triggerP,
null,
val.paramsP,
val.target_frame_idP,
val.calling_trigger_prop,
val.calling_program,
val.argumentsP,
val.prog_id,
val.nodeId,
val.parentDataSourceNo,
val.$container,
val.event_optionsP,
);
}
_this._was_busy = true;
} else {
// idle
if (_this._was_busy) {
if (glb.IS_WORKER) {
func.utils.post_back_to_client(SESSION_ID, 'worker_busy_off', _session.worker_id, null);
} else {
func.UI.utils.indicator.worker.normal();
}
}
_this._was_busy = false;
}
attempt = 0;
is_not_responding = false;
idle();
} else {
//busy
_this._was_busy = true;
if (glb.IS_WORKER) {
func.utils.post_back_to_client(SESSION_ID, 'worker_busy_on', _session.worker_id, null);
} else {
func.UI.utils.indicator.worker.busy();
}
if (glb.WORKER_PAUSE) return;
attempt++;
if (!is_progressScreen_on && attempt > glb.WORKER_TIMEOUT) busy();
if (!is_not_responding && attempt >= glb.WORKER_ATTEMPTS_NOT_RESPONDING) {
not_responding();
}
var ds = null;
if (_session.WORKER_OBJ.jobs[0]) ds = _session.WORKER_OBJ.jobs[0].dsSessionP;
lock(ds);
}
}, 1);
},
stop: function () {
clearInterval(this._interval);
},
};
};
func.utils.base64MimeType = function (encoded) {
var result = null;
if (typeof encoded !== 'string') {
return result;
}
var mime = encoded.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,.*/);
if (mime && mime.length) {
result = mime[1];
}
return result;
};
func.utils.makeid = function (length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
func.utils.get_device = function () {
var device;
try {
const win = func.runtime.platform.get_window();
if (win?.cordova) {
device = win.cordova.platformId;
}
} catch (e) {
console.error('error using ui element in server side request');
}
return device;
};
func.utils.ws_worker = {};
func.utils.ws_worker.functions = {
init: async function (data) {
var SESSION_ID = data.SESSION_ID;
APP_OBJ[data.app_id] = data.APP_OBJ;
PROJECT_OBJ[data.app_id] = data.PROJECT_OBJ;
if (['live_preview', 'miniapp'].includes(data.SESSION_INFO.engine_mode)) {
DOCS_OBJ[data.app_id] = data.DOCS_OBJ;
} else if (typeof IS_DOCKER === 'undefined' && typeof IS_PROCESS_SERVER === 'undefined') {
if (!DOCS_OBJ[data.app_id]) {
DOCS_OBJ[data.app_id] = {};
}
}
glb.APP_INFO[data.app_id] = data.APP_INFO;
glb.DEBUG_MODE = data.DEBUG_MODE;
glb.DEBUG_INFO_OBJ = data.DEBUG_INFO_OBJ;
glb.WINDOW_LOCATION_SEARCH = data.WINDOW_LOCATION_SEARCH;
glb.ROOT_ELEMENT_ATTRIBUTES = data.ROOT_ELEMENT_ATTRIBUTES;
DATASOURCE_INTERVALS[SESSION_ID] = {};
SESSION_OBJ[SESSION_ID] = data.SESSION_INFO;
var _session = SESSION_OBJ[SESSION_ID];
glb.SESSION_INFO = data.SESSION_INFO;
_session.engine_mode = data.engine_mode;
STUDIO_WEBSOCKET_CONNECTION_ID = data.STUDIO_WEBSOCKET_CONNECTION_ID;
for (let [key, val] of Object.entries(_session.DS_GLB)) {
if (Number(key) > _session.dataSourceSessionGlobal) {
_session.dataSourceSessionGlobal = Number(key);
}
}
if (typeof _session.SLIM_BUNDLE === 'undefined' || !_session.SLIM_BUNDLE) {
const db_adapter = await func.common.get_module(SESSION_ID, 'xuda-db-adapter-module.mjs');
func.db = db_adapter._db;
}
_session.WORKER_OBJ.fx = new func.utils.job_worker(SESSION_ID);
_session.WORKER_OBJ.fx.init();
if (_session.app_id === 'unknown') {
worker_post_message({
fx_to_execute: 'init_done',
worker_id: ws_worker_id,
session_id: SESSION_ID,
});
} else {
const module = await func.common.get_module(SESSION_ID, `xuda-progs-loader-module.mjs`);
await module.load_objects_cache(SESSION_ID);
worker_post_message({
fx_to_execute: 'init_done',
worker_id: ws_worker_id,
session_id: SESSION_ID,
});
}
WEB_WORKER_CALLBACK_QUEUE[SESSION_ID] = {};
},
datasource_create: async function (params, promise_queue_id) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
_session.ts = new Date().getTime();
var args = params;
args.SESSION_ID = SESSION_ID;
if (show_log) {
console.log('DATASOURCE EXECUTING SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
}
if (Number(params.dataSourceSessionGlobal) > _session.dataSourceSessionGlobal) {
_session.dataSourceSessionGlobal = Number(params.dataSourceSessionGlobal);
}
const ret = await func.datasource.prepare(
args.SESSION_ID,
args.prog_id,
args.dataSourceNoP,
args.parentDataSourceNoP,
args.containerIdP,
args.rowIdP,
args.jobNoP,
args.calling_trigger_prop,
args.parameters_raw_obj,
null,
args.callingSourceP,
args.calling_jobP,
args.screen_dsP,
args.is_panelP,
args.parameters_obj_inP,
args.static_refreshP,
args.run_atP,
args.worker_id,
);
try {
let _ds = _session.DS_GLB[ret.dsSessionP];
if (show_log) console.log('DATASOURCE EXECUTION DONE ' + ret.dsSessionP + ' ' + _ds?.tree_obj?.menuName || '' + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
var obj = func.utils.clean_returned_datasource(SESSION_ID, ret?.dsSessionP);
obj.dataSourceSessionGlobal = _session.dataSourceSessionGlobal;
worker_post_message({
promise_queue_id,
params: obj,
worker_id: ws_worker_id,
session_id: SESSION_ID,
process_pid: params.process_pid,
service: params.service,
});
_ds.stat = 'idle';
} catch (error) {
console.error('[xuda-runtime] caught xuda_utils.js:606:', error);
}
},
datasource_delete: function (params, promise_queue_id) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
if (DATASOURCE_INTERVALS[SESSION_ID] && DATASOURCE_INTERVALS[SESSION_ID][params.dssession]) {
DATASOURCE_INTERVALS[SESSION_ID][params.dssession].clear();
}
delete _session.DS_GLB[params.dssession];
if (show_log) console.log('DATASOURCE DELETE SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name, params.dssession);
console.error('[xuda-runtime] caught xuda_utils.js:617:', error);
worker_post_message({
promise_queue_id,
worker_id: ws_worker_id,
session_id: SESSION_ID,
process_pid: params.process_pid,
service: params.service,
});
},
update_datasource_changes_from_client: async function (params, promise_queue_id) {
if (xu_isEmpty(SESSION_OBJ)) return;
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
if (!_session) {
_session = {};
_session.app_id = params.app_id;
_session.dataSourceSessionGlobal = -1;
_session.DS_GLB = {};
}
if (show_log) console.log('DATASOURCE UPDATE SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name, params.dssession);
await func.datasource.update(SESSION_ID, params.datasource_changes, true);
worker_post_message({
promise_queue_id,
params: params.dssession,
worker_id: ws_worker_id,
session_id: SESSION_ID,
process_pid: params.process_pid,
service: params.service,
});
},
return_to_data_source: function (params, promise_queue_id) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
var ds = _session.DS_GLB[params.dssession];
var type = params.return_to_data_source_type;
var args = ds.args;
if (show_log) console.log('DATASOURCE RETURN TO DATASOURCE ' + params.dssession + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
_session.DS_GLB[params.dssession].v.onscreen_events_active = params.onscreen_events_active;
if (params.viewEventExec_arr) _session.DS_GLB[params.dssession].viewEventExec_arr = JSON.parse(params.viewEventExec_arr);
var done = function (SESSION_ID, DS) {
if (show_log) console.log('DATASOURCE RETURN TO DATASOURCE DONE ' + DS + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
var obj = func.utils.clean_returned_datasource(SESSION_ID, DS);
obj.dataSourceSessionGlobal = _session.dataSourceSessionGlobal;
worker_post_message({
fx_to_execute: 'post_datasource',
params: {
ds_obj: obj,
dsSessionP: params.dssession,
},
worker_id: ws_worker_id,
session_id: SESSION_ID,
process_pid: params.process_pid,
service: params.service,
});
};
done(SESSION_ID, params.dssession);
},
acknowledged_worker_with_eventChangesResults_done: function (params) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
var ds = _session.DS_GLB[params.dssession];
if (show_log) console.log('UPDATE CHANGE EVENT DONE TO DATASOURCE ' + params.dssession + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
ds.eventChangesResults_done = true;
},
return_from_db_query: function (params) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
var id = params.id;
if (show_log) console.log('RETURN FROM DB_QUERY ' + params.dssession + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
var callback = func.utils.get_callback_queue(SESSION_ID, params.callback_id);
if (callback) callback(params.data);
},
return_from_sava_data: function (params) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
var id = params.id;
if (show_log) console.log('RETURN FROM SAVE_DATA ' + params.dssession + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
func.utils.get_callback_queue(SESSION_ID, params.callback_id)();
},
update_debug_info: function (params) {
glb.DEBUG_INFO_OBJ = params;
},
// update_VIEWS_OBJ: function (params) {
// delete VIEWS_OBJ[APP_ID][params.id];
// },
// update_TREE_OBJ: function (params) {
// delete TREE_OBJ[APP_ID][params.id];
// },
// send_object_to_worker: function (params) {
// if (RESPONSE_FROM_STUDIO_QUEUE[params.req_id]) {
// RESPONSE_FROM_STUDIO_QUEUE[params.req_id].data = params;
// }
// },
get_dataSourceSessionGlobal: function (params) {
var SESSION_ID = params.session_id;
SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal++;
let new_dataSourceSessionGlobal = SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal;
return { new_dataSourceSessionGlobal };
},
create_webworker_globals: function (params) {
var SESSION_ID = params.session_id;
SESSION_OBJ[SESSION_ID].DS_GLB[0] = params.ds_data;
},
return_doc_from_studio: function (params) {
var SESSION_ID = params.session_id;
function emitCustomEvent(eventName, detail) {
const event = new CustomEvent(eventName, { detail });
self.dispatchEvent(event);
}
emitCustomEvent('live_preview_get_obj_response_worker_' + params._id, {
data: params,
});
},
return_doc_from_websocket: function (params) {
var SESSION_ID = params.session_id;
function emitCustomEvent(eventName, detail) {
const event = new CustomEvent(eventName, { detail });
self.dispatchEvent(event);
}
emitCustomEvent('get_doc_obj_from_build_worker_' + params._id, {
data: params,
});
},
return_dbs_data_from_websocket: function (params) {
var SESSION_ID = params.session_id;
function emitCustomEvent(eventName, detail) {
const event = new CustomEvent(eventName, { detail });
self.dispatchEvent(event);
}
emitCustomEvent('get_ws_data_worker_' + params.websocket_queue_num, {
data: params.data,
});
},
heartbeat: async function (params) {
var SESSION_ID = params.session_id;
try {
const do_heartbeat = async function (app_replicate, app_id, token_id, fingerprint, device_name, stat) {
try {
module.exports.close_expired_device_log_sessions(app_id);
return await update_device(app_replicate, app_id, token_id, fingerprint, device_name, stat);
} catch (err) {
return { code: -400, data: err.message };
}
};
let ret = await do_heartbeat(params.app_replicate, params.app_id, params.gtp_token || req.body.app_token, params.fingerprint, params.device_name, params.stat);
// session_status: ret_session.data.stat
if (params.token) {
try {
const couch = await __.rpi.get_app_couch(req.body.app_id);
const session_doc = await couch.get(req.body.app_token);
ret.session_stat = session_doc.stat;
} catch (error) {}
}
} catch (error) {
console.error('[xuda-runtime] caught xuda_utils.js:780:', error);
}
},
return_rpi_request_from_studio: function (params) {
var SESSION_ID = params.session_id;
function emitCustomEvent(eventName, detail) {
const event = new CustomEvent(eventName, { detail });
self.dispatchEvent(event);
}
emitCustomEvent('rpi_request_response_worker_' + params.table_id, {
data: params.data,
});
},
};
func.utils.set_callback_queue = function (SESSION_ID, func) {
var t = new Date().valueOf().toString() + Math.random().toString();
try {
WEB_WORKER_CALLBACK_QUEUE[SESSION_ID][t] = func;
} catch (e) {
console.error(id);
func.utils.remove_cached_objects(SESSION_ID);
}
return t;
};
func.utils.get_callback_queue = function (SESSION_ID, t) {
var func = WEB_WORKER_CALLBACK_QUEUE[SESSION_ID][t];
setTimeout(function () {
if (WEB_WORKER_CALLBACK_QUEUE[SESSION_ID][t]) delete WEB_WORKER_CALLBACK_QUEUE[SESSION_ID][t];
}, 1000);
return func;
};
func.utils.clean_stringify_null = function (key, value) {
// Filtering out properties
if (value === null) {
return undefined;
}
return value;
};
func.utils.load_js_on_demand = async function (js_src, type) {
const normalized_src = typeof js_src === 'string' ? js_src.trim() : '';
if (!normalized_src || normalized_src === 'undefined' || normalized_src === 'null') {
return false;
}
const get_script = function (callback) {
if (glb.IS_WORKER) {
callback();
return;
}
function isScriptLoaded(src) {
return GLB_JS_SCRIPTS_LOADED.includes(src);
}
if (isScriptLoaded(normalized_src)) {
callback(false);
} else {
func.runtime.platform.load_script(normalized_src, type, function () {
callback(true);
GLB_JS_SCRIPTS_LOADED.push(normalized_src);
});
}
};
return new Promise((resolve) => {
get_script(resolve);
});
};
func.utils.load_css_on_demand = function (css_href) {
const normalized_href = typeof css_href === 'string' ? css_href.trim() : '';
if (!normalized_href || normalized_href === 'undefined' || normalized_href === 'null') {
return null;
}
return func.runtime.platform.load_css(normalized_href);
};
func.utils.remove_js_css_file = function (filename, filetype) {
func.runtime.platform.remove_js_css(filename, filetype);
};
func.utils.replace_studio_drive_url = function (SESSION_ID, val) {
var _session = SESSION_OBJ[SESSION_ID];
if (!_session.is_deployment) return val;
try {
const rep = APP_OBJ[_session.app_id].app_replicate;
const dest = `https://${_session.domain}/studio-drive/${rep}`;
const rep_esc = rep.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Rewrite every legacy studio-drive host to the live domain: stale xuda.io hardcodings,
// and any xuda.ai host incl. subdomains (xuda.ai, master.xuda.ai, eu.xuda.ai, ...).
return val
.replaceAll(`https://xuda.io/studio-drive/${rep}`, dest)
.replace(new RegExp(`https://(?:[a-z0-9-]+\\.)*xuda\\.ai/studio-drive/${rep_esc}`, 'g'), dest);
} catch (err) {
return val;
}
};
func.utils.get_drive_url = function (SESSION_ID, val, wrap) {
// wrap = false; // tbd bypass
var _session = SESSION_OBJ[SESSION_ID];
function replaceFiletoURL(fileString) {
const _app = APP_OBJ[_session.app_id];
let url = `https://${_session.domain}/workspace-drive/${_app.is_deployment ? _app.app_datacenter_id : _app.app_id_reference}/`;
let FILE_REPLACE_URL = `${url}${val}`;
if (!_app.is_deployment) {
// the deployment server will handle the app_token
FILE_REPLACE_URL += `?app_token=${_session.app_token}&ts=${Date.now()}`;
} else {
FILE_REPLACE_URL += `?ts=${_session?.opt?.app_build_id || 0}`;
}
let match = `drv_${_app.app_replicate || _session.app_id}_[0-9a-f\\-]+\\.[a-zA-Z0-9]+`;
let pat = new RegExp(match, 'g');
let URLString = fileString.replace(pat, function (match, idx) {
const hasURLbefore = fileString.substring(idx - url.length, idx) === url;
if (hasURLbefore) {
return match;
}
return FILE_REPLACE_URL.replace('{val}', match);
});
return URLString;
}
if (typeof val === 'string' || typeof val === 'object') {
if (typeof val === 'string') {
if (val.includes('.') && val.includes('drv_') && val.length > 30) {
var ret = replaceFiletoURL(val);
if (wrap) {
return { value: '"' + ret + '"', changed: true };
} else {
return { value: ret, changed: true };
}
} else {
return { value: val, changed: false };
}
}
if (typeof val === 'object') {
// XU_PERF fast path: stringifying every bound object per evaluation just
// to look for 'drv_' is a hot-loop tax. Clean verdicts are memoized per
// object reference; datasource.update clears the cache, so a mutation can
// never outlive the refresh that would re-evaluate it. Only on a
// (possible) hit fall through to the exact legacy stringify path.
if (glb.XU_PERF) {
if (func.utils.drive_ref_clean_cache.has(val)) {
return { value: val, changed: false };
}
if (!func.utils.has_drive_ref(val, 0)) {
func.utils.drive_ref_clean_cache.add(val);
return { value: val, changed: false };
}
}
try {
let str = JSON.stringify(val);
if (str.includes('.') && str.includes('drv_') && str.length > 30) {
let new_val = replaceFiletoURL(str);
return { value: new_val, changed: true };
} else {
return { value: val, changed: false };
}
} catch (err) {
return { value: val, changed: false };
}
}
} else {
return { value: val, changed: false };
}
};
func.utils.drive_ref_clean_cache = new WeakSet();
// Early-exit scan for 'drv_' drive-file references in keys or string values.
// Returns true on any hit OR when traversal gives up (depth cap), so callers
// fall back to the exact legacy stringify detection in those cases.
func.utils.has_drive_ref = function (v, depth) {
if (typeof v === 'string') return v.includes('drv_');
if (v === null || typeof v !== 'object') return false;
if (depth > 8) return true; // too deep to be sure: let legacy path decide
if (Array.isArray(v)) {
for (let i = 0; i < v.length; i++) {
if (func.utils.has_drive_ref(v[i], depth + 1)) return true;
}
return false;
}
for (const k in v) {
if (k.includes('drv_')) return true;
if (func.utils.has_drive_ref(v[k], depth + 1)) return true;
}
return false;
};
func.utils._error_registry_module = func.utils._error_registry_module || null;
func.utils._error_registry_pending = func.utils._error_registry_pending || null;
func.utils.get_error_registry = async function (SESSION_ID) {
if (func.utils._error_registry_module) {
return func.utils._error_registry_module;
}
if (func.utils._error_registry_pending) {
return await func.utils._error_registry_pending;
}
if (!SESSION_ID || !SESSION_OBJ?.[SESSION_ID]) {
return null;
}
func.utils._error_registry_pending = func.common
.get_module(SESSION_ID, 'xuda-error-registry-module.mjs')
.then((module) => {
func.utils._error_registry_module = module;
return module;
})
.catch((err) => {
console.warn('XUDA WARNING RUN_MSG_NET_010', 'Failed to load error registry module', err);
return null;
})
.finally(() => {
func.utils._error_registry_pending = null;
});
return await func.utils._error_registry_pending;
};
func.utils._normalize_issue_severity = function (type, fallback = 'error') {
if (!type) return fallback;
const normalized = type.toString().toLowerCase();
if (['w', 'warn', 'warning'].includes(normalized)) return 'warning';
if (['i', 'info', 'log'].includes(normalized)) return 'info';
return 'error';
};
func.utils._serialize_issue_value = function (value, seen = new WeakSet()) {
if (value instanceof Error) {
return {
name: value.name,
message: value.message,
stack: value.stack,
cause: value.cause,
};
}
if (typeof value === 'undefined' || value === null) {
return value;
}
if (typeof value === 'function') {
return `[Function ${value.name || 'anonymous'}]`;
}
if (typeof value !== 'object') {
return value;
}
if (seen.has(value)) {
return '[Circular]';
}
seen.add(value);
if (Array.isArray(value)) {
return value.map((item) => func.utils._serialize_issue_value(item, seen));
}
const ret = {};
for (const [key, val] of Object.entries(value)) {
ret[key] = func.utils._serialize_issue_value(val, seen);
}
return ret;
};
func.utils._stringify_issue_message = function (value) {
if (typeof value === 'undefined' || value === null) {
return '';
}
if (typeof value === 'string') {
return value;
}
if (value instanceof Error) {
return value.message || value.toString();
}
try {
return JSON.stringify(func.utils._serialize_issue_value(value));
} catch (error) {
return value?.toString?.() || '';
}
};
func.utils._build_fallback_issue_definition = function (payload = {}) {
const code = payload.code || 'RUN_MSG_GEN_000';
const severity = func.utils._normalize_issue_severity(payload.type || payload.severity, code.startsWith('CHK_MSG_') ? 'warning' : 'error');
return {
code,
title: payload.title || (code.startsWith('CHK_MSG_') ? 'Studio Checker Validation Issue' : 'Runtime Error'),
severity,
domain: code.startsWith('CHK_MSG_') ? 'checker' : 'runtime',
category: payload.category || 'general',
summary: payload.message || 'The runtime reported an issue.',
help_slug: payload.help_slug || code.toLowerCase().replace(/_/g, '-'),
};
};
func.utils.report_issue = async function (SESSION_ID, payload = {}) {
const err = payload.err instanceof Error ? payload.err : payload.error instanceof Error ? payload.error : null;
const source = payload.source || payload.method || 'runtime';
const message = func.utils._stringify_issue_message(
typeof payload.message !== 'undefined' ? payload.message : typeof payload.msg !== 'undefined' ? payload.msg : err || payload.details || 'Unknown runtime issue',
);
try {
const registry = await func.utils.get_error_registry(SESSION_ID);
const registry_payload = {
code: payload.code,
source,
message,
type: payload.type || payload.severity,
err,
details: payload.details,
category: payload.category,
};
let definition = null;
if (registry?.get_error_definition && payload.code) {
definition = registry.get_error_definition(payload.code, registry_payload);
}
if (!definition && registry?.get_runtime_report_definition) {
definition = registry.get_runtime_report_definition(registry_payload);
}
if (!definition) {
definition = func.utils._build_fallback_issue_definition({
...payload,
source,
message,
});
}
const severity = func.utils._normalize_issue_severity(payload.type || payload.severity, definition.severity || 'error');
const error_code = definition.code || payload.code || 'RUN_MSG_GEN_000';
const error_title = definition.title || payload.title || 'Runtime Error';
const help_slug = definition.help_slug || error_code.toLowerCase().replace(/_/g, '-');
const console_method = severity === 'warning' ? 'warn' : severity === 'info' ? 'log' : 'error';
const _session = SESSION_OBJ?.[SESSION_ID];
const report = {
error_code,
error_title,
help_slug,
severity,
error_domain: definition.domain || 'runtime',
error_category: definition.category || payload.category || 'general',
source,
message,
stack: err?.stack || null,
app_id: _session?.app_id || payload.app_id || null,
session_id: SESSION_ID || null,
worker: !!glb?.IS_WORKER,
summary: definition.summary || message,
};
const details = {
report,
error: func.utils._serialize_issue_value(err),
context: func.utils._serialize_issue_value(payload.details),
extra: func.utils._serialize_issue_value(payload.extra),
};
const console_prefix = `XUDA ${console_method.toUpperCase()} ${error_code}`;
const console_summary = `${error_title}: ${source}${message ? ' | ' + message : ''}`;
if (glb?.debug_js && console.groupCollapsed) {
console.groupCollapsed(console_prefix, console_summary);
console[console_method](report);
if (details.context) console.log('context', details.context);
if (err) console.error(err);
console.groupEnd();
} else {
console[console_method](console_prefix, console_summary, {
help_slug,
});
}
if (!payload.skip_log && SESSION_ID && _session) {
await func.utils.write_log(SESSION_ID, source, message || error_title, console_method, payload.log_source || 'runtime', details, report);
}
return report;
} catch (report_err) {
console.error('XUDA ERROR RUN_MSG_GEN_000', 'Failed to report runtime issue', report_err, {
source,
message,
payload,
});
return null;
}
};
func.utils.debug_report = async function (SESSION_ID, sourceP, msgP, typeP, errP, objP) {
if (!typeP || typeP === 'E') {
setTimeout(() => {
// if (
// typeof IS_DOCKER === "undefined" &&
// typeof IS_API_SERVER === "undefined" &&
// typeof IS_PROCESS_SERVER === "undefined" &&
// !glb?.IS_WORKER
// ) {
// func.index.delete_pouch(SESSION_ID);
// }
}, 1000);
}
await func.utils.report_issue(SESSION_ID, {
source: sourceP,
message: msgP,
type: typeP,
err: errP,
details: objP,
});
};
func.utils.request_error = function (SESSION_ID, type, e) {
var _session = SESSION_OBJ[SESSION_ID];
console.error(type, e);
if (typeof IS_PROCESS_SERVER !== 'undefined') return;
if (!glb.IS_WORKER) {
func.utils.debug_report(SESSION_ID, type, e, 'E');
setTimeout(function () {
if (!glb.debug_js) {
// location.reload();
console.warn('** reload request');
}
}, 2000);
} else {
func.utils.post_back_to_client(SESSION_ID, 'ajax_error', _session.worker_id, null);
}
};
func.utils.alerts = {};
func.utils.alerts.invoke = async function (SESSION_ID, typeP, paramsP, sourceP, dsSessionP, msgP) {
try {
var _session = SESSION_OBJ[SESSION_ID];
if (ALERT_IS_ACTIVE) return;
ALERT_IS_ACTIVE = true;
var title;
var message = '';
var alert_type = 'console';
var alertDisplay;
var expRet = {};
var _ds = _session.DS_GLB[dsSessionP];
var type = '';
var createLog;
const get_alert_properties = async function (value, fx) {
var ret = value || '';
if (fx) {
const exp_ret = await func.expression.get(SESSION_ID, fx, dsSessionP, 'alert');
ret = exp_ret.result;
}
return ret;
};
switch (typeP) {
case 'alert':
type = 'User defined alert';
title = await get_alert_properties(paramsP.alertTitle, paramsP.alertTitleFx);
alert_type = await get_alert_properties(paramsP.alertType, paramsP.alertTypeFx);
message = await get_alert_properties(paramsP.alertBody, paramsP.alertBodyFx);
alertDisplay = await get_alert_properties(paramsP.alertDisplay, paramsP.alertDisplayFx);
createLog = paramsP.createLog;
break;
case 'call_alert':
type = 'User defined call alert';
let prop = await func.utils.TREE_OBJ.get(SESSION_ID, paramsP.prog);
if (!prop) {
console.log('events.execute', 'Missing details for alert message object: ' + paramsP.prog, 'W');
}
// user defined
let ret = await func.utils.VIEWS_OBJ.get(SESSION_ID, paramsP.prog);
if (ret?.alertData) {
title = await get_alert_properties(ret.alertData.alertTitle, ret.alertData.alertTitleFx);
alert_type = await get_alert_properties(ret.alertData.alertType, ret.alertData.alertTypeFx);
message = await get_alert_properties(ret.alertData.alertBody, ret.alertData.alertBodyFx);
alertDisplay = await get_alert_properties(ret.alertData.alertDisplay, ret.alertData.alertDisplayFx);
createLog = ret.alertData.createLog;
}
if (!title) {
title = prop.menuTitle;
}
if (!alert_type) {
alert_type = 'console';
}
if (!alertDisplay) {
alertDisplay = 'modal';
} //window alert by definition
break;
case 'system_msg': {
type = 'System alert';
const sys_alerts_obj = func.utils.get_system_error_msg();
if (sys_alerts_obj[paramsP]) {
title = sys_alerts_obj[paramsP].subject;
alert_type = sys_alerts_obj[paramsP].alert_type;
alertDisplay = sys_alerts_obj[paramsP].alertDisplay;
expRet = await func.expression.get(SESSION_ID, sys_alerts_obj[paramsP].msg, dsSessionP, 'alert');
message = func.expression.remove_quotes(expRet.result);
if (msgP) message = msgP;
if (alert_type === 'error') {
if (_ds) _ds.error = title + ' ' + sourceP;
func.utils.debug_report(SESSION_ID, sourceP, title + ' ' + sourceP, 'E', '', _ds);
}
}
break;
}
default:
message = msgP;
break;
}
} catch (err) {
console.error(err);
ALERT_IS_ACTIVE = false;
return;
}
if (glb.IS_WORKER) {
if (_session.IS_API) {
if (_ds) {
_ds.api_rendered_output = message;
} else {
console.error(message);
}
return;
}
// if (typeof IS_PROCESS_SERVER !== "undefined") return;
ALERT_IS_ACTIVE = false;
return func.utils.post_back_to_client(SESSION_ID, 'alert', _session.worker_id, [SESSION_ID, alert_type, alertDisplay, message, title]);
}
// const sys_alerts_obj = func.utils.get_system_error_msg();
// if (sys_alerts_obj[paramsP]) {
// var str = paramsP + " " + title + " " + message;
// if (sys_alerts_obj[paramsP].alert_type === "error") {
// console.error(str);
// } else if (sys_alerts_obj[paramsP].alert_type === "warning") {
// console.warn(str);
// } else {
// console.log(str);
// }
// } else {
// console.log(str);
// }
ALERT_IS_ACTIVE = false;
func.utils.alerts.execute(SESSION_ID, alert_type, alertDisplay, message, title, type);
if (createLog) {
func.utils.write_log(SESSION_ID, title, message, alert_type);
}
};
func.utils.alerts.execute = function (SESSION_ID, alert_type, alertDisplay, message, title, type) {
if (!UI_FRAMEWORK_INSTALLED) {
ALERT_IS_ACTIVE = false;
if (alertDisplay !== 'console') {
return alert(title + '\n \n' + message);
}
return console[alert_type === 'error' ? 'error' : 'log'](alert_type, title, message);
}
switch (alertDisplay) {
case 'console':
console[alert_type === 'success' ? 'log' : alert_type === 'warning' ? 'warn' : alert_type](alert_type, title, message);
ALERT_IS_ACTIVE = false;
break;
case 'modal':
func.utils.alerts.popup(title, message, alert_type);
break;
case 'toast':
func.utils.alerts.toast(SESSION_ID, title, message, alert_type);
ALERT_IS_ACTIVE = false;
break;
case 'browser':
alert(title + '\n \n' + message);
ALERT_IS_ACTIVE = false;
default:
console.log(alert_type, title, message);
ALERT_IS_ACTIVE = false;
}
};
func.utils.alerts.toast = function (SESSION_ID, title, message, alert_type) {
if (!UI_FRAMEWORK_PLUGIN.toast) return;
const toast = new UI_FRAMEWORK_PLUGIN.toast();
toast.create(alert_type, message, title, func.common.get_url(SESSION_ID, 'dist', `runtime/images/${alert_type}_alert_ico.svg`));
ALERT_IS_ACTIVE = false;
};
func.utils.alerts.popup = function (title, message, alert_type) {
const popup = new UI_FRAMEWORK_PLUGIN.popup();
var buttons = [
{
text: 'Ok',
role: 'cancel',
// cssClass: "primary",
handler: () => {
ALERT_IS_ACTIVE = false;
},
},
];
popup.create(alert_type.charAt(0).toUpperCase() + alert_type.slice(1), title, message, buttons);
};
func.utils.get_system_error_msg = function () {
var m = {};
m['SYS_MSG_0101'] = {
alert_type: 'success',
alertDisplay: 'toast',
subject: 'Save Success',
msg: 'Settings successfully saved',
};
m['SYS_MSG_0102'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Save Failed CouchDB',
msg: 'Data fail save to database',
};
m['SYS_MSG_0103'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Save Failed Table Empty',
msg: 'Table empty, no fields declared',
};
m['SYS_MSG_0104'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Save Failed Missing Primary Index',
msg: 'Update failed, table missing Primary index',
};
m['SYS_MSG_0105'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Save Failed Table Missing',
msg: 'Table repository missing',
};
m['SYS_MSG_0106'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Save Failed Record Not Exist',
msg: 'Save update failed record not exist',
};
m['SYS_MSG_0107'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Save Failed Unique Key',
msg: 'Save Failed, record already exist',
};
m['SYS_MSG_0108'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Error reading document',
msg: 'Save Failed, record not found',
};
m['SYS_MSG_0110'] = {
alert_type: 'warning',
alertDisplay: 'toast',
subject: 'Record Changed',
msg: 'Record changed by other user, reload to get the latest changes',
};
m['SYS_MSG_0120'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Create Mode Denied',
msg: 'Create mode not allowed for this program',
};
m['SYS_MSG_0122'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Modify Mode Denied',
msg: 'Modify mode not allowed for this program',
};
m['SYS_MSG_0124'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Delete Mode Denied',
msg: 'Delete mode not allowed for this program',
};
m['SYS_MSG_0126'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Program Read Only',
msg: 'Program set to Read Only',
};
m['SYS_MSG_0130'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Error Reduce',
msg: 'Select Index to Reduce',
};
m['SYS_MSG_0201'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Failed to change GUI Property',
msg: 'Failed to change GUI element property, GUI element missing',
};
m['SYS_MSG_0310'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Missing Reference Parameters Out',
msg: 'Parameter out not exist in dataset',
};
m['SYS_MSG_0400'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Delete Widget Folder Denied',
msg: 'The selected folder contains data, Please clean or move content to another folder',
};
m['SYS_MSG_0410'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Recipient Error',
msg: 'Check recipient data',
};
m['SYS_MSG_0412'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Recipient Empty',
msg: 'No recipients entered or selected',
};
m['SYS_MSG_0414'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Data Save Error',
msg: 'Widget has no content',
};
m['SYS_MSG_0416'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Required Field',
msg: 'Edit url field is empty',
};
m['SYS_MSG_0418'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Required Field',
msg: 'Publish url field is empty',
};
m['SYS_MSG_0420'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Connection Error',
msg: 'Cannot connect to mailbox',
};
m['SYS_MSG_0422'] = {
alert_type: 'success',
alertDisplay: 'modal',
subject: 'Connection Ok',
msg: 'Connection Ok :)',
};
m['SYS_MSG_0424'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Connection Failed',
msg: 'Connection to POP3 failed',
};
m['SYS_MSG_0426'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Connection Failed',
msg: 'SMTP Connection error, Test Email was not sent',
};
m['SYS_MSG_0430'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Email Account Error',
msg: 'No email account found, Right Click Tree -> Settings->Manage Accounts -> Right click for menu options',
};
m['SYS_MSG_0440'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Widget Initiation Error',
msg: 'Missing information for Link Type or Link Name',
};
m['SYS_MSG_0442'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Error Init Widget',
msg: 'Missing record Id on Create Mode',
};
m['SYS_MSG_0450'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Validation Failed',
msg: 'Fix fields highlight in Red',
};
m['SYS_MSG_0501'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Mandatory Alert Save',
msg: 'Save action failed, Mandatory fields missing',
};
m['SYS_MSG_0550'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Illegal input number',
msg: "@SYS_GLOBAL_OBJ_ACTIVE_FIELD_INFO.nameform +' only allow numbers!'",
};
m['SYS_MSG_0610'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Form Field Conflict',
msg: 'Field declared more than once for the form',
};
m['SYS_MSG_0612'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Missing Definition',
msg: 'Missing mask definition',
};
m['SYS_MSG_0614'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Wrong Definition',
msg: 'Wrong mask definition',
};
m['SYS_MSG_0616'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Size Parser',
msg: 'Size parser error',
};
m['SYS_MSG_0618'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Illegal Z switch',
msg: "Illegal 'Z' in string mask",
};
m['SYS_MSG_0620'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Illegal N switch',
msg: "Illegal 'N' in string mask",
};
m['SYS_MSG_0622'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Illegal + switch',
msg: "Illegal '+' in string mask",
};
m['SYS_MSG_0624'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Illegal - switch',
msg: "Illegal '-' in string mask",
};
m['SYS_MSG_0626'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Illegal C switch',
msg: "Illegal 'C' in string mask",
};
m['SYS_MSG_0628'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Invalid switch',
msg: 'Invalid switch in string mask',
};
m['SYS_MSG_0630'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Missing DOM Element',
msg: 'Missing DOM element',
};
m['SYS_MSG_0632'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Too Big',
msg: 'Size to big, max: 15.5',
};
m['SYS_MSG_0700'] = {
alert_type: 'warning',
alertDisplay: 'console',
subject: 'Table Warning - Empty',
msg: 'Table has no content',
};
m['SYS_MSG_0702'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - No Fields',
msg: 'Table missing fields content',
};
m['SYS_MSG_0704'] = {
alert_type: 'warning',
alertDisplay: 'console',
subject: 'Table Warning - Not In Use',
msg: 'Table not in use by any object',
};
m['SYS_MSG_0706'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - No Primary Index',
msg: 'Table must have at least one index',
};
m['SYS_MSG_0708'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Bad Index Name',
msg: 'Index name is invalid or cannot contain any of non word characters',
};
m['SYS_MSG_0710'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Empty Index',
msg: 'Index has no keys',
};
m['SYS_MSG_0712'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Key Not Exist',
msg: 'Key not exist in the table fields repository',
};
m['SYS_MSG_0714'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Duplicate Fields',
msg: 'Duplicate fields in the table fields repository',
};
m['SYS_MSG_0716'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Bad Field Name',
msg: 'Field name is invalid or cannot contain any of non word characters',
};
m['SYS_MSG_0718'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Integrity Broken',
msg: 'Field broken from its properties, edit the field and save',
};
m['SYS_MSG_0720'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Model Not Exist',
msg: 'Model assigned to the field not exist',
};
m['SYS_MSG_0722'] = {
alert_type: 'warning',
alertDisplay: 'console',
subject: 'Object Warning - Not In Use',
msg: 'Object not in use or not call by any object',
};
m['SYS_MSG_0724'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Table Not Exist',
msg: 'Table assigned in object datasource not exist',
};
m['SYS_MSG_0726'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Keys Mismatch',
msg: 'Table index has different structure',
};
m['SYS_MSG_0728'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Key From Empty',
msg: 'Index key From must have a value',
};
m['SYS_MSG_0730'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Key From Reference',
msg: 'Field reference not exist in any dataset or parameters',
};
m['SYS_MSG_0732'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Key To Empty',
msg: 'Index key To must have a value',
};
m['SYS_MSG_0734'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Key To Reference',
msg: 'Field reference not exist in any dataset or parameters',
};
m['SYS_MSG_0736'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Locate From Reference',
msg: 'Field reference not exist in any dataset or parameters',
};
m['SYS_MSG_0738'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Locate To Reference',
msg: 'Field reference not exist in any dataset or parameters',
};
m['SYS_MSG_0740'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Empty',
msg: 'Index empty - no keys defined',
};
m['SYS_MSG_0742'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Reference',
msg: 'Table index reference error',
};
m['SYS_MSG_0744'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Duplicate Fields',
msg: 'Duplicate fields in the dataset fields repository',
};
m['SYS_MSG_0746'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Field reference error',
msg: 'Field not exist in datasource table fields repository',
};
m['SYS_MSG_0748'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Mismatch Reference Type',
msg: 'Mismatch in calling reference type',
};
m['SYS_MSG_0750'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Reference Broken',
msg: 'Reference broken calling object not exist',
};
m['SYS_MSG_0752'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Empty Reference',
msg: 'Reference empty',
};
m['SYS_MSG_0754'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Action Not Exist',
msg: 'Action not exist',
};
m['SYS_MSG_0756'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Empty Event Reference',
msg: 'Empty event reference',
};
m['SYS_MSG_0758'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Bad Field Name',
msg: 'Field name is invalid or cannot contain any of non word characters',
};
m['SYS_MSG_0760'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Integrity Broken',
msg: 'Field broken from its properties, edit the object and save',
};
m['SYS_MSG_0762'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Model Not Exist',
msg: 'Model assigned to the field not exist',
};
m['SYS_MSG_0764'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Empty Dataset',
msg: 'Dataset empty from fields',
};
m['SYS_MSG_0766'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Field Type Mismatch',
msg: 'Field type not match to the underlined table field definition',
};
m['SYS_MSG_0768'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Field Mask Mismatch',
msg: 'Field masks not match to the underlined table field definition',
};
m['SYS_MSG_0770'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Bad Event Name',
msg: 'Event name is invalid or cannot contain any of non word characters',
};
m['SYS_MSG_0772'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - UI Field Not Exist',
msg: 'UI Field not exist in the dataset repository',
};
m['SYS_MSG_0774'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - UI Field Reference Broken',
msg: 'UI Field reference broken',
};
m['SYS_MSG_0780'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'UI element error',
msg: 'UI element not exist',
};
m['SYS_MSG_1210'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Program error',
msg: 'Program not exist',
};
m['SYS_MSG_1220'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Program error',
msg: 'Non grid output defined',
};
m['SYS_MSG_1240'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Debug error',
msg: '',
};
m['SYS_MSG_1250'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Debug log error',
msg: '',
};
m['SYS_MSG_1260'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Session Expired',
msg: 'Renew token session in Studio',
};
return m;
};
func.utils.find_key_in_ViewUITreeObj = function (arr, key, val) {
return arr.reduce((a, item) => {
if (a) return a;
if (item[key] === val) return item;
if (item.children) return findId(val, item.children);
}, null);
};
func.utils.get_plugin_setup = function (SESSION_ID, plugin_name) {
const _session = SESSION_OBJ[SESSION_ID];
const normalize_setup_response = function (value) {
if (value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'code')) {
return value;
}
return {
code: 1,
data: value && typeof value === 'object' ? value : {},
};
};
const report_error = function (descP, warn) {
func.utils.debug.log(SESSION_ID, plugin_name, {
module: 'plugin',
action: 'Init',
source: 'get_plugin_setup',
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'plugin',
});
};
return new Promise(async (resolve, reject) => {
try {
const db = await func.utils.connect_pouchdb(SESSION_ID);
const should_bypass_cache =
_session?.worker_type === 'Dev' ||
(_session?.engine_mode === 'miniapp' && !_session?.app_token);
if (!should_bypass_cache) {
try {
let ret = await db.get(`cache_plugin_setup_${plugin_name}`);
return resolve(normalize_setup_response(ret.data));
} catch (err) {
// cache miss
}
}
const json = normalize_setup_response(await func.common.db(SESSION_ID, 'get_plugin_setup', {
plugin_name,
}));
if (json.code < 0) {
report_error('Error: ' + json.data, json.error_type === 'W' ? true : false);
}
resolve(json);
if (!should_bypass_cache) {
var doc = {
_id: `cache_plugin_setup_${plugin_name}`,
data: json,
docType: 'cache_plugin',
};
db.put(doc);
}
} catch (e) {
console.error(e);
const error_message = e?.message || e?.msg || String(e || 'Unknown plugin setup error');
report_error('Error: ' + error_message, e?.error_type === 'W' ? true : false);
resolve({
code: -1,
data: error_message,
error_type: e?.error_type,
});
}
});
};
func.utils.connect_studio_pouchdb = function (app_id, rt, custom) {
if (custom) {
return new PouchDB(custom, { auto_compaction: true });
}
var db_name = 'xuda_studio_db';
if (app_id) {
db_name += '_' + app_id;
}
if (rt) {
db_name = `xuda_rt_${app_id}`;
}
return new PouchDB(db_name, { auto_compaction: true });
};
func.utils.connect_pouchdb = async function (SESSION_ID) {
const app_id = SESSION_OBJ[SESSION_ID].app_id;
return func.utils.connect_studio_pouchdb(app_id, true);
};
func.utils.base64_encode_utf8 = function (value = '') {
if (typeof btoa === 'function') {
return btoa(unescape(encodeURIComponent(value)));
}
if (typeof Buffer !== 'undefined') {
return Buffer.from(value, 'utf8').toString('base64');
}
throw new Error('base64 encoder unavailable');
};
func.utils.should_use_local_studio_plugin_resources = function (SESSION_ID) {
const _session = SESSION_OBJ[SESSION_ID];
return typeof IS_PROCESS_SERVER === 'undefined' && (['live_preview'].includes(_session?.engine_mode) || _session?.is_draft_runtime);
};
func.utils.get_local_studio_plugin_doc = async function (SESSION_ID, plugin_name) {
if (!func.utils.should_use_local_studio_plugin_resources(SESSION_ID)) {
return null;
}
try {
const db = func.utils.connect_studio_pouchdb(null, false, 'xuda_studio_resources');
return await db.get(plugin_name);
} catch (error) {
return null;
}
};
func.utils.get_local_studio_plugin_file = async function (SESSION_ID, plugin_name, resource) {
const plugin_doc = await func.utils.get_local_studio_plugin_doc(SESSION_ID, plugin_name);
return plugin_doc?.files?.[`${plugin_name}/${resource}`] || null;
};
func.utils.get_local_studio_plugin_resource_url = async function (SESSION_ID, plugin_name, resource) {
const file_contents = await func.utils.get_local_studio_plugin_file(SESSION_ID, plugin_name, resource);
if (!file_contents) {
return null;
}
const content_type = resource.endsWith('.css') ? 'text/css' : 'text/javascript';
return `data:${content_type};base64,${func.utils.base64_encode_utf8(file_contents)}`;
};
// func.utils.validate_pouchdb = async function (SESSION_ID) {
// const app_id = SESSION_OBJ[SESSION_ID].app_id;
// const db = new PouchDB("xuda_rt_" + app_id);
// // db.info()
// // .then((info) => {
// // console.log("Database exists", info);
// // })
// // .catch((error) => {
// // if (error.status === 404) {
// // throw new Error("Database does not exist");
// // } else {
// // throw new Error("Error accessing database", error);
// // }
// // });
// try {
// let ret = await db.get(`cache_rt_info`);
// } catch (error) {
// throw new Error("new pouch detect");
// }
// };
func.utils.call_plugin_api = function (SESSION_ID, plugin_nameP, dataP) {
var _session = SESSION_OBJ[SESSION_ID];
const report_error = function (descP, warn) {
func.utils.debug.log(SESSION_ID, plugin_nameP, {
module: 'plugin',
action: 'Init',
source: 'call_plugin_api',
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'plugin',
});
};
return new Promise(async (resolve) => {
var data = {
app_id: APP_OBJ[_session.app_id]._id,
debug: glb.DEBUG_MODE,
uid: _session.USR_OBJ._id,
gtp_token: _session.gtp_token,
app_token: _session.app_token,
};
data = Object.assign(data, dataP);
fetch(`https://xuda.ai/ppi/${plugin_nameP}`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then((response) => {
if (!response.ok) {
return response.text().then((text) => {
throw new Error(text);
});
}
return response.json();
})
.then((json) => {
if (json.code < 0) {
report_error('Error: ' + json.data, json.error_type === 'W' ? true : false);
}
resolve(json.data);
// var doc = {
// _id: `cache_plugin_${plugin_nameP}`,
// data: json,
// docType: "cache_plugin",
// };
// db.put(doc);
})
.catch((err) => {
report_error('Error: ' + err.message);
resolve(err.message);
});
// }
});
};
func.utils.get_plugin_resource = function (SESSION_ID, plugin_name, plugin_resource) {
var _session = SESSION_OBJ[SESSION_ID];
const resource_ts =
(typeof globalThis !== 'undefined' ? globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ : '') ||
_session?.build_info?.runtime_ts ||
_session?.build_info?.last_changed_ts ||
_session?.build_info?.server_ts ||
_session?.opt?.app_build_id ||
(typeof globalThis !== 'undefined' ? globalThis.__XU_SERVER_BOOTSTRAP__?.version : 0) ||
0;
const plugin_resource_ts = `${resource_ts}-plugin-20260505-1`;
const get_path = function (resource) {
const server_origin = typeof globalThis !== 'undefined' ? globalThis.__XU_SERVER_ORIGIN__ : '';
if (server_origin) {
return `${server_origin}/plugins/${plugin_name}/${resource}?app_id=${_session.app_id}&ts=${plugin_resource_ts}`;
}
if (_session.worker_type === 'Dev') {
return `../../plugins/${plugin_name}/${resource}?ts=${plugin_resource_ts}`;
}
if (typeof IS_PROCESS_SERVER !== 'undefined') {
return `${_conf.plugins_drive_path}/${_session.app_id}/node_modules/${plugin_name}/${resource}`;
} else {
// return `./node_modules/${plugin_name}/${resource}?app_id=${_session.app_id}`;
return `https://${_session.domain}/plugins/${plugin_name}/${resource}?app_id=${_session.app_id}&ts=${plugin_resource_ts}`;
}
};
return new Promise(async (resolve, reject) => {
try {
const local_plugin_resource_url = await func.utils.get_local_studio_plugin_resource_url(SESSION_ID, plugin_name, plugin_resource);
if (local_plugin_resource_url) {
const plugin_resource_res = await import(/* @vite-ignore */ local_plugin_resource_url);
return resolve(plugin_resource_res);
}
} catch (err) {}
// const db = await func.utils.connect_pouchdb(SESSION_ID);
// try {
// if (_session.worker_type === "Dev") throw "bypass cache";
// let ret = await db.get(`cache_plugin_${plugin_name}_${plugin_resource}`);
// return resolve(ret.data);
// } catch (err) {
// try {
// const plugin_resource_res = await import(
// `${get_path(plugin_resource)}`
// );
// resolve(plugin_resource_res);
// // var doc = {
// // _id: `cache_plugin_${plugin_name}_${plugin_resource}`,
// // data: JSON.stringify(plugin_resource_res),
// // docType: "cache_plugin",
// // };
// // db.put(doc);
// } catch (err) {
// console.error(err);
// }
// }
try {
const plugin_resource_res = await import(`${get_path(plugin_resource)}`);
resolve(plugin_resource_res);
} catch (err) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_GUI_020',
source: 'func.utils.get_plugin_resource',
message: 'plugin setup import failed',
err,
details: {
plugin_name,
plugin_resource,
plugin_path: get_path(plugin_resource),
},
});
reject(err);
}
});
};
func.utils.remove_cached_objects = async function (SESSION_ID) {
if (typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') return;
try {
const db = await func.utils.connect_pouchdb(SESSION_ID);
let opt = {
$or: [
{
docType: 'cache_objects',
},
{
docType: 'cache_plugin',
},
{
docType: 'cache_app',
},
{
docType: 'cache_build_info',
},
],
};
const res = await db.find({ selector: opt });
for await (let val of res.docs) {
await db.remove(val);
}
} catch (err) {
return;
}
};
func.utils.get_plugin_npm_cdn = async function (SESSION_ID, plugin_name, resource) {
const _session = SESSION_OBJ[SESSION_ID];
const resource_ts =
(typeof globalThis !== 'undefined' ? globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ : '') ||
_session?.build_info?.runtime_ts ||
_session?.build_info?.last_changed_ts ||
_session?.build_info?.server_ts ||
_session?.opt?.app_build_id ||
(typeof globalThis !== 'undefined' ? globalThis.__XU_SERVER_BOOTSTRAP__?.version : 0) ||
0;
const plugin_resource_ts = `${resource_ts}-plugin-20260505-1`;
const local_plugin_resource_url = await func.utils.get_local_studio_plugin_resource_url(SESSION_ID, plugin_name, resource);
if (local_plugin_resource_url) {
return local_plugin_resource_url;
}
const get_path = function (resource) {
const server_origin = typeof globalThis !== 'undefined' ? globalThis.__XU_SERVER_ORIGIN__ : '';
if (server_origin) {
return `${server_origin}/plugins/${plugin_name}/${resource}?app_id=${_session.app_id}&ts=${plugin_resource_ts}`;
}
if (_session.worker_type === 'Dev') {
return `../../plugins/${plugin_name}/${resource}?ts=${plugin_resource_ts}`;
}
return `https://${_session.domain}/plugins/${plugin_name}/${resource}?app_id=${_session.app_id}&ts=${plugin_resource_ts}`;
};
return get_path(resource);
};
func.utils.write_log = async function (SESSION_ID, method = '', msg = '', log_type = 'error', source = 'runtime', details, meta = {}) {
const _session = SESSION_OBJ[SESSION_ID];
const body = {
msg,
log_type,
source,
details,
method,
...meta,
};
const is_offline =
(typeof IS_ONLINE !== 'undefined' && !IS_ONLINE) ||
(typeof navigator !== 'undefined' && navigator.onLine === false);
if (_session?.is_draft_runtime && is_offline) {
return null;
}
if (typeof IS_API_SERVER !== 'undefined' || typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
return __.rpi.write_log(
_session?.app_id,
log_type,
source,
msg,
details,
null,
body,
method,
_session?.SYS_GLOBAL_OBJ_CLIENT_INFO?.fingerprint,
);
}
if (glb.IS_WORKER) {
let obj = {
service: 'write_log',
data: body,
log_type,
id: STUDIO_WEBSOCKET_CONNECTION_ID,
uid: _session?.USR_OBJ?._id,
source,
app_id: _session?.app_id,
gtp_token: _session?.gtp_token,
app_token: _session?.app_token,
};
return func.utils.post_back_to_client(SESSION_ID, 'write_log', _session.worker_id, obj);
}
await func.common.db(SESSION_ID, 'write_log', body);
};
func.utils.get_error_catalog_manifest = async function (SESSION_ID) {
const registry = await func.utils.get_error_registry(SESSION_ID);
return registry?.get_error_catalog_manifest?.() || null;
};
func.utils.get_resource_filename = function (build, filename) {
if (build) {
return filename.replace(/(\.\w+)$/, `.${build}$1`);
}
return filename;
};
func.utils.set_SYS_GLOBAL_OBJ_WIDGET_INFO = async function (SESSION_ID, docP) {
var obj = { ...docP };
obj.date = await func.utils.get_dateTime(SESSION_ID, 'SYS_DATE', docP.date);
obj.time = await func.utils.get_dateTime(SESSION_ID, 'SYS_TIME', docP.date);
var datasource_changes = {
[0]: {
['data_system']: {
['SYS_GLOBAL_OBJ_WIDGET_INFO']: obj,
},
},
};
await func.datasource.update(SESSION_ID, datasource_changes);
};
func.utils.get_last_datasource_no = function (SESSION_ID) {
if (typeof IS_PROCESS_SERVER !== 'undefined') {
return Object.keys(SESSION_OBJ[SESSION_ID].DS_GLB).at?.(-1);
} else {
const filtered = Object.values(SESSION_OBJ[SESSION_ID].DS_GLB).filter((e) => e.tree_obj.menuType !== 'api');
return filtered?.at?.(-1)?.dsSession;
}
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only host, root, and screen-container helpers live here so low-level DOM/container code can stay focused.
func.runtime.ui.get_root_element = function (SESSION_ID) {
const _session = SESSION_OBJ[SESSION_ID];
if (!_session) {
return func.runtime.ui._wrap_matches([]);
}
if (_session._root_wrapped?.[0] === _session.root_element) {
return _session._root_wrapped;
}
_session._root_wrapped = func.runtime.ui._wrap_matches(_session.root_element ? [_session.root_element] : []);
return _session._root_wrapped;
};
func.runtime.ui.get_root_node = function (SESSION_ID) {
return func.runtime.ui.get_root_element(SESSION_ID)?.[0] || null;
};
func.runtime.ui.show_root_element = function (SESSION_ID) {
return func.runtime.ui.show(func.runtime.ui.get_root_element(SESSION_ID));
};
func.runtime.ui.ensure_app_shell = function (SESSION_ID, domain) {
const $root_element = func.runtime.ui.get_root_element(SESSION_ID);
func.runtime.ui.set_style($root_element, 'position', 'relative');
if (!func.runtime.ui.has_selector($root_element, '.loader')) {
func.runtime.ui.append_html($root_element, `
<style>
.loader {
position: absolute;
background: rgb(0 0 0 / 30%);
z-index: 10;
overflow-y: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.loader .loader_logo {
width: 43px;
height: 43px;
border-radius: 50%;
background-size: cover;
}
.loader .loader_msg {
padding-top: 10px;
text-align: center;
min-height: 20px;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
.loader .loader_rotate {
padding: 20px;
border: 2px solid #fff;
border-right-color: #898989;
border-radius: 22px;
-webkit-animation: rotate 0.8s infinite linear;
position: absolute;
}
@-webkit-keyframes rotate {
100% {
-webkit-transform: rotate(360deg);
}
}
</style>
<div class="loader">
<div class="loader_logo">
<div class="loader_rotate"></div>
</div>
<div class="loader_msg"> </div>
</div>
<div class="progressLoader"></div>
`);
}
if (!func.runtime.ui.has_selector($root_element, '#tailwind_toast_controller')) {
func.runtime.ui.append_html(
$root_element,
`<div aria-live="assertive"
class="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start z-[999]">
<div id="tailwind_toast_controller" class="w-full flex flex-col items-center space-y-4 sm:items-end">
</div>
</div>`,
);
}
if (!func.runtime.ui.has_selector($root_element, '#progressScreen2')) {
func.runtime.ui.append_html(
$root_element,
`
<div id="progressScreen2" style="display: none">
<div id="progressScreen2_text"></div>
</div>`,
);
}
const get_url = function (domain, method, path) {
return `https://${domain}/${method}${path ? '/' + path : '/'}`;
};
func.utils.load_css_on_demand(get_url(domain, 'dist', 'runtime/css/mobile.css'));
return $root_element;
};
func.runtime.ui.ensure_embed_container = function (SESSION_ID) {
const $root_element = func.runtime.ui.get_root_element(SESSION_ID);
let $embed_container = func.runtime.ui.find_by_selector($root_element, `#embed_${SESSION_ID}`, true);
if (!$embed_container.length) {
const $ssr_embed_container = func.runtime.ui.find_by_selector($root_element, `[data-xuda-ssr-embed="true"]`, true);
const ssr_embed_node = func.runtime.ui.get_first_node($ssr_embed_container);
if (ssr_embed_node) {
ssr_embed_node.id = 'embed_' + SESSION_ID;
$embed_container = func.runtime.ui._wrap_matches([ssr_embed_node]);
}
}
if (!$embed_container.length) {
const embed_node = document.createElement('div');
embed_node.id = 'embed_' + SESSION_ID;
embed_node.className = 'xu_embed_div';
func.runtime.ui.set_data(embed_node, 'xuData', {});
$embed_container = func.runtime.ui._wrap_matches([embed_node]);
func.runtime.ui.append_to($embed_container, $root_element);
}
return $embed_container;
};
func.runtime.ui.get_embed_container = function (SESSION_ID) {
return func.runtime.ui.find_by_selector(func.runtime.ui.get_root_element(SESSION_ID), `#embed_${SESSION_ID}`, true);
};
func.runtime.ui.get_embed_screen_containers = function () {
return func.runtime.ui.find_by_selector(document.body, '.xu_embed_container');
};
func.runtime.ui.append_to_body = function ($element) {
func.runtime.ui.append_to($element, document.body);
return $element;
};
func.runtime.ui.find_in_root = function (SESSION_ID, selector) {
return func.runtime.ui.find_by_selector(func.runtime.ui.get_root_element(SESSION_ID), selector);
};
func.runtime.ui.get_root_tag_name = function () {
let root_tag_name = 'div';
if (!func.runtime.session.is_slim()) {
if (typeof UI_FRAMEWORK_PLUGIN?.core !== 'function') {
return root_tag_name;
}
const ui_plugin_core = new UI_FRAMEWORK_PLUGIN.core();
root_tag_name = ui_plugin_core?.rootTagName() || root_tag_name;
}
return root_tag_name;
};
func.runtime.ui.find_ssr_screen_host = function ($container, screenId, containerId) {
const container_node = func.runtime.ui.get_first_node($container);
if (!container_node || !screenId) {
return null;
}
const dialog_node =
container_node.querySelector?.(`#${CSS?.escape ? CSS.escape(screenId) : screenId}`) ||
container_node.querySelector?.(`[data-xuda-ssr-screen-id="${screenId}"]`);
if (!dialog_node) {
return null;
}
const root_frame_node =
(containerId ? dialog_node.querySelector?.(`#${CSS?.escape ? CSS.escape(containerId) : containerId}`) : null) ||
dialog_node.querySelector?.('[data-xuda-ssr-root-frame="true"]');
if (!root_frame_node) {
return null;
}
return {
$dialogDiv: func.runtime.ui._wrap_matches([dialog_node]),
$rootFrame: func.runtime.ui._wrap_matches([root_frame_node]),
reused_ssr_host: true,
};
};
func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params, $callingContainerP, screenId) {
var $dialogDiv;
var $rootFrame;
let reused_ssr_host = false;
switch (screen_type) {
case 'embed': {
const ssr_host = func.runtime.ui.find_ssr_screen_host($callingContainerP, screenId, params?.containerIdP);
if (ssr_host) {
return ssr_host;
}
const dialogNode = document.createElement('div');
dialogNode.id = screenId;
dialogNode.setAttribute('ui_engine', UI_FRAMEWORK_INSTALLED);
dialogNode.classList.add('xu_embed_container');
dialogNode.style.display = 'contents';
func.runtime.ui.set_data(dialogNode, 'xuData', {
paramsP: params,
screenInfo: params.screenInfo,
});
$dialogDiv = func.runtime.ui._wrap_matches([dialogNode]);
const root_tag_name = func.runtime.ui.get_root_tag_name();
const rootFrameNode = document.createElement(root_tag_name);
func.runtime.ui.set_data(rootFrameNode, 'xuData', {});
func.runtime.ui.set_data(rootFrameNode, 'xuAttributes', {});
dialogNode.appendChild(rootFrameNode);
$rootFrame = func.runtime.ui._wrap_matches([rootFrameNode]);
func.runtime.ui.append_to($dialogDiv, $callingContainerP);
break;
}
case 'panel':
$dialogDiv = $callingContainerP;
func.runtime.ui.set_data($dialogDiv, 'xuData', {
paramsP: params,
screenInfo: params.screenInfo,
});
$rootFrame = $dialogDiv;
break;
case 'page':
case 'modal':
case 'popover': {
const dialogNode = document.createElement('div');
const rootFrameNode = document.createElement('div');
dialogNode.appendChild(rootFrameNode);
$dialogDiv = func.runtime.ui._wrap_matches([dialogNode]);
$rootFrame = func.runtime.ui._wrap_matches([rootFrameNode]);
func.runtime.ui.append_to_body($dialogDiv);
break;
}
default:
break;
}
return {
$dialogDiv,
$rootFrame,
reused_ssr_host,
};
};
func.runtime.ui.find_xu_ui_in_root = function (SESSION_ID, xu_ui_id) {
if (!SESSION_ID || !xu_ui_id) {
return func.runtime.ui._wrap_matches([]);
}
if (func.runtime.ui.get_refresh_indexed_element_by_ui_id) {
const elm = func.runtime.ui.get_refresh_indexed_element_by_ui_id(SESSION_ID, xu_ui_id);
return func.runtime.ui._wrap_matches(elm ? [elm] : []);
}
return func.runtime.ui.find_in_root(SESSION_ID, `[xu-ui-id="${xu_ui_id}"]`);
};
func.runtime.ui.find_panel_wrapper_in_root = function (SESSION_ID, xu_ui_id) {
if (func.runtime.ui.get_refresh_indexed_panel_wrapper_by_id) {
const elm = func.runtime.ui.get_refresh_indexed_panel_wrapper_by_id(SESSION_ID, xu_ui_id);
return func.runtime.ui._wrap_matches(elm ? [elm] : []);
}
return func.runtime.ui.find_in_root(SESSION_ID, `[xu-panel-wrapper-id=${xu_ui_id}]`);
};
func.runtime.ui.find_element_data_in_root = function (SESSION_ID, dataKey, property, value) {
if (dataKey === 'xuData' && property === 'ui_id' && typeof value !== 'undefined' && func.runtime.ui.get_refresh_indexed_element_by_ui_id) {
const elm = func.runtime.ui.get_refresh_indexed_element_by_ui_id(SESSION_ID, value);
return func.runtime.ui._wrap_matches(elm ? [elm] : []);
}
if (dataKey === 'xuData' && property === 'nodeid' && typeof value !== 'undefined' && func.runtime.ui.get_refresh_indexed_elements_by_node_id) {
return func.runtime.ui.get_refresh_indexed_elements_by_node_id(SESSION_ID, value);
}
if (dataKey === 'xuPanelWrapper' && property === 'isWrapper' && typeof value === 'undefined') {
if (func.runtime.ui.get_refresh_indexed_panel_wrappers) {
return func.runtime.ui.get_refresh_indexed_panel_wrappers(SESSION_ID);
}
return func.runtime.ui.find_in_root(SESSION_ID, '[xu-panel-wrapper-id]');
}
return func.UI.utils.find_in_element_data(dataKey, func.runtime.ui.get_root_element(SESSION_ID), property, value);
};
func.runtime.ui.find_element_data_in_parent = function ($container, dataKey, property, value) {
const container_node = func.runtime.ui.get_first_node($container);
const parent_node = container_node?.parentElement;
if (!parent_node) {
return func.runtime.ui._wrap_matches([]);
}
if (dataKey === 'xuData' && property === 'ui_id' && typeof value !== 'undefined') {
if (func.runtime.ui.find_refresh_elements_by_attr) {
return func.runtime.ui.find_refresh_elements_by_attr(parent_node, 'xu-ui-id', value, true);
}
return func.runtime.ui.find_by_selector(parent_node, `[xu-ui-id="${value}"]`);
}
return func.UI.utils.find_in_element_data(dataKey, func.runtime.ui._wrap_matches([parent_node]), property, value);
};
func.runtime.ui.sync_child_parent_container = function ($div) {
const div_node = func.runtime.ui.get_first_node($div);
const parent_container = func.runtime.ui.get_data(div_node)?.xuData?.parent_container;
const children = div_node?.children ? Array.from(div_node.children) : [];
for (let index = 0; index < children.length; index++) {
const child_data = func.runtime.ui.get_data(children[index]);
if (!child_data?.xuData?.parent_container) {
continue;
}
child_data.xuData.parent_container = parent_container;
}
return $div;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
func.runtime.ui.ui_id_hash_cache = func.runtime.ui.ui_id_hash_cache || new Map();
func.runtime.ui.node_snapshot_cache = func.runtime.ui.node_snapshot_cache || new WeakMap();
func.runtime.ui.node_child_items_cache = func.runtime.ui.node_child_items_cache || new WeakMap();
func.runtime.ui.node_children_by_id_cache = func.runtime.ui.node_children_by_id_cache || new WeakMap();
func.runtime.ui.is_invalid_asset_url_value = function (value) {
const normalized_value = typeof value === 'string' ? value.trim() : '';
return !normalized_value || /^(undefined|null)$/i.test(normalized_value) || /url\([^)]*(undefined|null)[^)]*\)/i.test(normalized_value);
};
if (typeof CSSStyleDeclaration !== 'undefined' && !CSSStyleDeclaration.prototype.__xuda_safe_asset_url_guard) {
const background_image_descriptor = Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, 'backgroundImage');
const original_set_property = CSSStyleDeclaration.prototype.setProperty;
Object.defineProperty(CSSStyleDeclaration.prototype, 'backgroundImage', {
configurable: true,
enumerable: background_image_descriptor?.enumerable ?? true,
get() {
if (background_image_descriptor?.get) {
return background_image_descriptor.get.call(this);
}
return this.getPropertyValue('background-image');
},
set(value) {
if (func.runtime.ui.is_invalid_asset_url_value(value)) {
return this.removeProperty('background-image');
}
if (background_image_descriptor?.set) {
return background_image_descriptor.set.call(this, value);
}
return original_set_property.call(this, 'background-image', value);
},
});
CSSStyleDeclaration.prototype.setProperty = function (name, value, priority) {
if (String(name).toLowerCase() === 'background-image' && func.runtime.ui.is_invalid_asset_url_value(value)) {
return this.removeProperty('background-image');
}
return original_set_property.call(this, name, value, priority);
};
Object.defineProperty(CSSStyleDeclaration.prototype, '__xuda_safe_asset_url_guard', { value: true });
}
// ── DOM-independent metadata store ──
// Keyed by xu-ui-id, decouples element metadata from jQuery .data().
// Enables headless/SSR execution where DOM elements don't exist.
func.runtime.ui._meta_store = func.runtime.ui._meta_store || {};
func.runtime.ui._element_id_to_xu_ui_id = func.runtime.ui._element_id_to_xu_ui_id || {};
func.runtime.ui.set_meta = function (xu_ui_id, key, value) {
if (!xu_ui_id) return;
if (!func.runtime.ui._meta_store[xu_ui_id]) {
func.runtime.ui._meta_store[xu_ui_id] = {};
}
func.runtime.ui._meta_store[xu_ui_id][key] = value;
};
func.runtime.ui.get_meta = function (xu_ui_id, key) {
const entry = func.runtime.ui._meta_store[xu_ui_id];
if (!entry) return undefined;
return key ? entry[key] : entry;
};
func.runtime.ui.delete_meta = function (xu_ui_id) {
delete func.runtime.ui._meta_store[xu_ui_id];
// clean reverse lookup
for (const id in func.runtime.ui._element_id_to_xu_ui_id) {
if (func.runtime.ui._element_id_to_xu_ui_id[id] === xu_ui_id) {
delete func.runtime.ui._element_id_to_xu_ui_id[id];
}
}
};
func.runtime.ui.register_element_id = function (element_id, xu_ui_id) {
if (element_id && xu_ui_id) {
func.runtime.ui._element_id_to_xu_ui_id[element_id] = xu_ui_id;
}
};
func.runtime.ui.get_meta_by_element_id = function (element_id) {
if (!element_id) return undefined;
const xu_ui_id = func.runtime.ui._element_id_to_xu_ui_id[element_id];
if (xu_ui_id) {
return func.runtime.ui._meta_store[xu_ui_id];
}
// fallback: try DOM lookup if in browser
if (typeof document !== 'undefined') {
const clean_id = element_id.startsWith('#') ? element_id.substring(1) : element_id;
const el = document.getElementById(clean_id);
if (el) {
const dom_xu_ui_id = el.getAttribute('xu-ui-id');
if (dom_xu_ui_id) {
func.runtime.ui.register_element_id(clean_id, dom_xu_ui_id);
return func.runtime.ui._meta_store[dom_xu_ui_id];
}
return func.runtime.ui.get_data(el);
}
}
return undefined;
};
func.runtime.ui.find_element_by_id = function (element_id) {
if (!element_id) return null;
if (typeof document !== 'undefined') {
return document.getElementById(element_id) || null;
}
return null;
};
func.runtime.ui.get_parent_element_id = function (element_id) {
if (!element_id) {
return null;
}
const clean_id = element_id.startsWith('#') ? element_id.substring(1) : element_id;
const element = func.runtime.ui.find_element_by_id(clean_id);
return element?.parentElement?.id || null;
};
func.runtime.ui.get_session_root = function (SESSION_ID) {
if (typeof document !== 'undefined') {
return document.getElementById('embed_' + SESSION_ID) || null;
}
return null;
};
func.runtime.ui.clear_screen_blockers = function () {
if (typeof document !== 'undefined') {
const blockers = document.querySelectorAll('.screen_blocker');
for (let i = 0; i < blockers.length; i++) {
blockers[i].remove();
}
}
};
func.runtime.ui.as_jquery = function (target) {
const node = func.runtime.ui.get_first_node(target);
return func.runtime.ui._wrap_matches(node ? [node] : []);
};
func.runtime.ui.get_first_node = function (target) {
if (!target) {
return null;
}
if (target?.nodeType) {
return target;
}
if (Array.isArray(target) || typeof target?.length === 'number') {
return target[0] || null;
}
return null;
};
func.runtime.ui.get_data = function (target) {
const target_node = func.runtime.ui.get_first_node(target);
if (target_node?.getAttribute) {
const xu_ui_id = target_node.getAttribute('xu-ui-id');
if (xu_ui_id) {
const meta = func.runtime.ui._meta_store[xu_ui_id];
if (meta) return meta;
}
}
if (target_node) {
if (!target_node.__xuData) {
target_node.__xuData = {};
}
return target_node.__xuData;
}
return {};
};
func.runtime.ui.get_parent = function (target) {
const target_node = func.runtime.ui.get_first_node(target);
return target_node?.parentElement || null;
};
func.runtime.ui.get_children = function (target) {
const target_node = func.runtime.ui.get_first_node(target);
return target_node?.children ? Array.from(target_node.children) : [];
};
// _wrap_matches — build a results object compatible with both jQuery-style
// access (.length, [0], [1], .toArray()) and native iteration (for...of).
func.runtime.ui._wrap_matches = function (matches) {
if (!matches) matches = [];
const result = {
length: matches.length,
toArray: function () { return matches.slice(); },
};
for (let i = 0; i < matches.length; i++) {
result[i] = matches[i];
}
// Make it iterable with for...of
result[Symbol.iterator] = function () {
let idx = 0;
return {
next: function () {
if (idx < matches.length) {
return { value: matches[idx++], done: false };
}
return { done: true };
},
};
};
return result;
};
func.runtime.ui.find_by_selector = function (target, selector, first_only = false) {
const target_node = func.runtime.ui.get_first_node(target);
const root_nodes = target_node ? [target_node] : (Array.isArray(target) ? target : (target?.length ? Array.from(target) : []));
const matches = [];
for (let root_index = 0; root_index < root_nodes.length; root_index++) {
const root_node = root_nodes[root_index];
if (root_node?.matches?.(selector)) {
matches.push(root_node);
if (first_only) {
break;
}
}
if (first_only && matches.length) {
break;
}
if (first_only) {
const first_match = root_node?.querySelector?.(selector);
if (first_match) {
matches.push(first_match);
break;
}
continue;
}
const descendants = root_node?.querySelectorAll?.(selector) || [];
for (let index = 0; index < descendants.length; index++) {
matches.push(descendants[index]);
}
}
return func.runtime.ui._wrap_matches(matches);
};
func.runtime.ui.insert_before = function ($element, $reference) {
const element_node = func.runtime.ui.get_first_node($element);
const reference_node = func.runtime.ui.get_first_node($reference);
if (reference_node?.before && element_node) {
reference_node.before(element_node);
}
if (func.runtime.ui.mark_refresh_index_dirty_from_target) {
func.runtime.ui.mark_refresh_index_dirty_from_target($reference, func.runtime.ui.get_data($element)?.xuData?.SESSION_ID);
}
return $element;
};
func.runtime.ui.insert_after = function ($element, $reference) {
const element_node = func.runtime.ui.get_first_node($element);
const reference_node = func.runtime.ui.get_first_node($reference);
if (reference_node?.after && element_node) {
reference_node.after(element_node);
}
if (func.runtime.ui.mark_refresh_index_dirty_from_target) {
func.runtime.ui.mark_refresh_index_dirty_from_target($reference, func.runtime.ui.get_data($element)?.xuData?.SESSION_ID);
}
return $element;
};
func.runtime.ui.has_selector = function (target, selector) {
return !!func.runtime.ui.get_first_node(func.runtime.ui.find_by_selector(target, selector, true));
};
func.runtime.ui.append_html = function (target, html) {
const target_node = func.runtime.ui.get_first_node(target);
if (!target_node || !html) {
return target_node || null;
}
target_node.insertAdjacentHTML('beforeend', html);
if (func.runtime.ui.mark_refresh_index_dirty_from_target) {
func.runtime.ui.mark_refresh_index_dirty_from_target(target_node);
}
return target_node;
};
func.runtime.ui.set_style = function (target, prop, value) {
const target_node = func.runtime.ui.get_first_node(target);
if (!target_node?.style || !prop) {
return target_node || null;
}
target_node.style[prop] = value;
return target_node;
};
func.runtime.ui.normalize_external_asset_url = function (value) {
if (typeof value !== 'string' || !value.trim()) {
return value;
}
const trimmed_value = value.trim();
try {
const url = new URL(trimmed_value, typeof document !== 'undefined' ? document.baseURI : 'https://xuda.ai');
if (url.hostname === 'via.placeholder.com') {
url.hostname = 'placehold.co';
return url.toString();
}
} catch (_) {
if (trimmed_value.startsWith('https://via.placeholder.com/') || trimmed_value.startsWith('http://via.placeholder.com/')) {
return trimmed_value.replace('via.placeholder.com', 'placehold.co');
}
}
return value;
};
func.runtime.ui.normalize_svg_path_value = function (value) {
if (typeof value !== 'string' || !value.trim()) {
return value;
}
let normalized_value = value.trim();
// Common AI-generated typo: path data ends with a dangling command after closepath, e.g. "...zzm".
normalized_value = normalized_value.replace(/([zZ])+\s*[mMlLhHvVcCsSqQtTaA]\s*$/g, '$1');
return normalized_value;
};
func.runtime.ui.normalize_attr_value = function (target_or_tag, key, value) {
if (typeof value !== 'string') {
return value;
}
const target_tag_name =
typeof target_or_tag === 'string'
? target_or_tag
: func.runtime.ui.get_first_node(target_or_tag)?.tagName || '';
const tag_name = `${target_tag_name || ''}`.trim().toLowerCase();
const attr_name = `${key || ''}`.trim().toLowerCase();
if ((tag_name === 'img' || tag_name === 'image') && ['src', 'href', 'xlink:href'].includes(attr_name)) {
return func.runtime.ui.normalize_external_asset_url(value);
}
if (tag_name === 'path' && attr_name === 'd') {
return func.runtime.ui.normalize_svg_path_value(value);
}
return value;
};
func.runtime.ui.get_attr = function (target, key) {
const target_node = func.runtime.ui.get_first_node(target);
return target_node?.getAttribute?.(key) ?? undefined;
};
func.runtime.ui.should_remove_attr = function (key, value) {
if (typeof value === 'undefined' || value === null) {
return true;
}
if (typeof value === 'string') {
const normalized_value = value.trim().toLowerCase();
if (normalized_value === 'undefined' || normalized_value === 'null') {
return true;
}
}
if (glb.solid_attributes?.includes?.(key)) {
return value === false || value === 'false' || value === 0 || value === '0';
}
return false;
};
func.runtime.ui.remove_attr = function (target, key) {
const target_node = func.runtime.ui.get_first_node(target);
target_node?.removeAttribute?.(key);
return target_node || target;
};
func.runtime.ui.set_attr = function (target, key, value) {
const target_node = func.runtime.ui.get_first_node(target);
if (target_node?.setAttribute) {
if (func.runtime.ui.should_remove_attr(key, value)) {
target_node.removeAttribute?.(key);
return target_node;
}
const normalized_value = glb.solid_attributes?.includes?.(key) && value === true
? ''
: func.runtime.ui.normalize_attr_value(target_node, key, value);
try {
target_node.setAttribute(key, normalized_value);
} catch (error) {
console.warn(`Failed to set attribute "${key}" on <${target_node.tagName?.toLowerCase?.() || 'unknown'}>`, error);
}
}
return target_node || target;
};
func.runtime.ui.set_data = function (target, key, value) {
const target_node = func.runtime.ui.get_first_node(target);
// write to meta store via xu-ui-id
if (target_node?.getAttribute) {
const xu_ui_id = target_node.getAttribute('xu-ui-id');
if (xu_ui_id) {
func.runtime.ui.set_meta(xu_ui_id, key, value);
return target;
}
}
// fallback: store on node directly
if (target_node) {
if (!target_node.__xuData) target_node.__xuData = {};
target_node.__xuData[key] = value;
}
return target;
};
func.runtime.ui.clear_data = function (target) {
const target_node = func.runtime.ui.get_first_node(target);
if (target_node?.getAttribute) {
const xu_ui_id = target_node.getAttribute('xu-ui-id');
if (xu_ui_id) {
func.runtime.ui.delete_meta(xu_ui_id);
}
}
if (target_node?.__xuData) {
delete target_node.__xuData;
}
return target;
};
func.runtime.ui.add_class = function (target, class_name) {
const target_node = func.runtime.ui.get_first_node(target);
if (target_node?.classList) {
target_node.classList.add(class_name);
return target_node;
}
return target_node || null;
};
func.runtime.ui.remove_class = function (target, class_name) {
const target_node = func.runtime.ui.get_first_node(target);
if (target_node?.classList) {
target_node.classList.remove(class_name);
return target_node;
}
return target_node || null;
};
func.runtime.ui.set_html = function (target, value) {
const target_node = func.runtime.ui.get_first_node(target);
if (typeof target_node?.innerHTML !== 'undefined') {
target_node.innerHTML = value;
return target_node;
}
return target_node || null;
};
func.runtime.ui.set_text = function (target, value) {
const target_node = func.runtime.ui.get_first_node(target);
if (typeof target_node?.textContent !== 'undefined') {
target_node.textContent = value;
return target_node;
}
return target_node || null;
};
func.runtime.ui.show = function (target) {
const target_node = func.runtime.ui.get_first_node(target);
if (target_node?.style) {
target_node.style.removeProperty('display');
return target_node;
}
return target_node || null;
};
func.runtime.ui.hide = function (target) {
const target_node = func.runtime.ui.get_first_node(target);
if (target_node?.style) {
target_node.style.display = 'none';
return target_node;
}
return target_node || null;
};
func.runtime.ui.append = function ($target, $element) {
const target_node = func.runtime.ui.get_first_node($target);
const element_node = func.runtime.ui.get_first_node($element);
if (target_node && element_node) {
target_node.appendChild(element_node);
}
if (func.runtime.ui.mark_refresh_index_dirty_from_target) {
func.runtime.ui.mark_refresh_index_dirty_from_target($target, func.runtime.ui.get_data($element)?.xuData?.SESSION_ID);
}
func.runtime?.perf?.increment?.(func.runtime.ui.get_data($element)?.xuData?.SESSION_ID, 'dom_appends');
return $element;
};
func.runtime.ui.append_to = function ($element, $target) {
const target_node = func.runtime.ui.get_first_node($target);
const element_node = func.runtime.ui.get_first_node($element);
if (target_node && element_node) {
target_node.appendChild(element_node);
}
if (func.runtime.ui.mark_refresh_index_dirty_from_target) {
func.runtime.ui.mark_refresh_index_dirty_from_target($target, func.runtime.ui.get_data($element)?.xuData?.SESSION_ID);
}
func.runtime?.perf?.increment?.(func.runtime.ui.get_data($element)?.xuData?.SESSION_ID, 'dom_appends');
return $element;
};
func.runtime.ui.empty = function (target) {
const target_node = func.runtime.ui.get_first_node(target);
if (target_node) {
target_node.replaceChildren();
}
if (func.runtime.ui.mark_refresh_index_dirty_from_target) {
func.runtime.ui.mark_refresh_index_dirty_from_target(target);
}
return target_node || null;
};
func.runtime.ui.remove = function (target) {
if (func.runtime.ui.mark_refresh_index_dirty_from_target) {
func.runtime.ui.mark_refresh_index_dirty_from_target(target);
}
// Normalise target into an array of DOM nodes
let target_nodes;
if (target?.nodeType) {
target_nodes = [target];
} else if (Array.isArray(target)) {
target_nodes = target;
} else if (target?.length) {
target_nodes = Array.from(target);
} else {
target_nodes = [];
}
const session_id = func.runtime.ui.get_data(target_nodes[0] || target)?.xuData?.SESSION_ID;
for (let index = 0; index < target_nodes.length; index++) {
const target_node = target_nodes[index];
// clean meta store for removed elements
const xu_ui_id = target_node?.getAttribute?.('xu-ui-id');
if (xu_ui_id) {
func.runtime.ui.delete_meta(xu_ui_id);
}
if (target_node?.remove) {
target_node.remove();
}
}
func.runtime?.perf?.increment?.(session_id, 'dom_removes', target_nodes.length || 1);
return true;
};
func.runtime.ui.set_display_contents = function ($element) {
return func.runtime.ui.set_style($element, 'display', 'contents');
};
func.runtime.ui.reconcile_xu_ui_id_duplicates = function (SESSION_ID, xu_ui_id, $keep) {
// Identity contract of the render layer: at most ONE live element may carry a given
// xu-ui-id. Re-render passes that miss their swap (stale $elm reference / refresh-index
// miss) used to leave both the old content and the new placeholder — or duplicate content
// copies — in the DOM (e.g. an un-closable modal whose gate flag was already false).
// Panels and teleports have their own reconcile (find_panel_wrapper_in_root /
// reconcile_teleports); this is the equivalent for xu-render swaps.
if (!xu_ui_id) return 0;
const keep_node = $keep ? func.runtime.ui.get_first_node($keep) : null;
const root_node = func.runtime.ui.get_first_node(func.runtime.ui.get_root_element?.(SESSION_ID)) || document.body;
if (!root_node?.querySelectorAll) return 0;
const escaped_ui_id = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(xu_ui_id) : xu_ui_id;
const matches = root_node.querySelectorAll(`[xu-ui-id="${escaped_ui_id}"]`);
// UI.remove deletes the meta-store entry of every element it removes — and the store is
// keyed by xu-ui-id, which the keeper SHARES with the duplicates being removed. Without
// restoring it, the surviving element becomes a lifecycle orphan: no ui_type/xuAttributes
// means the refresh index can't see it and its gate field can no longer reach it (an
// un-closable modal). Hold the entry reference and put it back after the removals.
const kept_meta = keep_node ? func.runtime.ui._meta_store?.[xu_ui_id] : null;
let removed = 0;
for (let match_index = 0; match_index < matches.length; match_index++) {
const match_node = matches[match_index];
if (keep_node && (match_node === keep_node || keep_node.contains(match_node) || match_node.contains(keep_node))) continue;
func.runtime.ui.remove(match_node);
removed++;
}
if (removed && keep_node && kept_meta && func.runtime.ui._meta_store && !func.runtime.ui._meta_store[xu_ui_id]) {
func.runtime.ui._meta_store[xu_ui_id] = kept_meta;
}
return removed;
};
func.runtime.ui.create_xurender = function (xu_ui_id, $target, hidden) {
const xurender = document.createElement('xurender');
xurender.setAttribute('xu-ui-id', xu_ui_id);
if (hidden) {
xurender.setAttribute('hidden', 'true');
}
return func.runtime.ui.append_to(xurender, $target);
};
func.runtime.ui.replace_with = function ($source, $target) {
if (func.runtime.ui.mark_refresh_index_dirty_from_target) {
func.runtime.ui.mark_refresh_index_dirty_from_target($source, func.runtime.ui.get_data($source)?.xuData?.SESSION_ID || func.runtime.ui.get_data($target)?.xuData?.SESSION_ID);
}
const source_node = func.runtime.ui.get_first_node($source);
const target_node = func.runtime.ui.get_first_node($target);
if (source_node?.replaceWith && target_node) {
source_node.replaceWith(target_node);
}
return $target;
};
func.runtime.ui.remove_xu_ui = function (xu_ui_id) {
const $targets = func.runtime.ui.find_by_selector(document.body, `[xu-ui-id="${xu_ui_id}"]`, false);
if (func.runtime.ui.mark_refresh_index_dirty_from_target) {
func.runtime.ui.mark_refresh_index_dirty_from_target($targets);
}
const targets = $targets.toArray();
for (let index = 0; index < targets.length; index++) {
targets[index].remove?.();
}
return true;
};
func.runtime.ui.build_debug_info = function (nodeP, $container, items) {
return {
id: nodeP.id,
parent_id: func.runtime.ui.get_data($container)?.xuData?.ui_id,
items: items,
};
};
func.runtime.ui.get_node_snapshot = function (nodeP) {
if (!nodeP) {
return nodeP;
}
if (func.runtime.ui.node_snapshot_cache.has(nodeP)) {
return func.runtime.ui.node_snapshot_cache.get(nodeP);
}
const snapshot = structuredClone(nodeP);
func.runtime.ui.node_snapshot_cache.set(nodeP, snapshot);
return snapshot;
};
func.runtime.ui.get_node_child_items = function (nodeP) {
if (!nodeP?.children?.length) {
return [];
}
if (func.runtime.ui.node_child_items_cache.has(nodeP)) {
return func.runtime.ui.node_child_items_cache.get(nodeP);
}
const items = nodeP.children.map(function (val) {
return val.xu_tree_id || val.id;
});
func.runtime.ui.node_child_items_cache.set(nodeP, items);
return items;
};
func.runtime.ui.get_node_children_by_id = function (nodeP) {
if (!nodeP?.children?.length) {
return {};
}
if (func.runtime.ui.node_children_by_id_cache.has(nodeP)) {
return func.runtime.ui.node_children_by_id_cache.get(nodeP);
}
const children_by_id = {};
for (let index = 0; index < nodeP.children.length; index++) {
const child_node = nodeP.children[index];
if (child_node?.id) {
children_by_id[child_node.id] = child_node;
}
}
func.runtime.ui.node_children_by_id_cache.set(nodeP, children_by_id);
return children_by_id;
};
func.runtime.ui.build_container_xu_data = function (options) {
return {
SESSION_ID: options.SESSION_ID,
prog_id: options.paramsP.prog_id,
nodeid: options.nodeP.id,
ui_type: options.nodeP.tagName,
recordid: options.currentRecordId,
paramsP: options.paramsP,
key: options.keyP,
key_path: options.key_path,
screenId: options.paramsP.screenId,
parent_container: func.runtime.ui.get_attr(options.$container, 'id'),
elem_key: options.elem_key,
properties: options.prop,
node: options.nodeP,
node_org: func.runtime.ui.get_node_snapshot(options.nodeP),
is_panelP: options.paramsP.is_panelP,
ui_id: options.ui_id,
elem_prop: options.elem_propP,
debug_info: func.runtime.ui.build_debug_info(options.nodeP, options.$container, options.items),
parent_node: options.parent_nodeP,
currentRecordId: options.currentRecordId,
$root_container: options.$root_container,
parent_element_ui_id: func.runtime.ui.get_data(options.$container)?.xuData?.ui_id,
is_placeholder: !!options.is_placeholder,
};
};
func.runtime.ui.apply_container_meta = function ($div, options) {
const div_node = func.runtime.ui.get_first_node($div);
func.runtime.ui.set_attr(div_node, 'xu-ui-id', options.ui_id);
func.runtime.ui.set_attr(div_node, 'data-xuda-kind', options.treeP?.kind || options.nodeP?.tagName || 'element');
func.runtime.ui.set_attr(div_node, 'data-xuda-node-id', options.nodeP?.id || options.nodeP?.id_org || '');
if (options.treeP?.meta?.tree_id !== null && typeof options.treeP?.meta?.tree_id !== 'undefined') {
func.runtime.ui.set_attr(div_node, 'data-xuda-tree-id', options.treeP.meta.tree_id);
}
const xuData = func.runtime.ui.build_container_xu_data(options);
if (options.parent_infoP?.iterate_info) {
xuData.iterate_info = options.parent_infoP.iterate_info;
}
func.runtime.ui.set_data(div_node, 'xuData', xuData);
func.runtime.ui.set_data(div_node, 'xuAttributes', {});
// dual-write to meta store
func.runtime.ui.set_meta(options.ui_id, 'xuData', xuData);
func.runtime.ui.set_meta(options.ui_id, 'xuAttributes', {});
// register element id mapping for lookups by container/screen id
const container_id = func.runtime.ui.get_attr(div_node, 'id');
if (container_id) {
func.runtime.ui.register_element_id(container_id, options.ui_id);
}
if (options.is_placeholder) {
func.runtime.ui.add_class(div_node, 'display_none');
}
if (options.classP) {
func.runtime.ui.add_class(div_node, options.classP);
}
return $div;
};
func.runtime.ui.get_append_target = function ($container, $appendToP) {
const $appendTo = $appendToP || $container;
if (!$appendTo) {
return null;
}
// Support both native elements (check nodeType) and jQuery/array-like (check length)
const node = func.runtime.ui.get_first_node($appendTo);
if (!node) {
return null;
}
return $appendTo;
};
func.runtime.ui.create_element = function (tag_name, attr_str) {
const el = document.createElement(tag_name);
if (attr_str) {
// Parse attribute string: key="value" or key='value' or bare key
const attr_regex = /([a-zA-Z_][\w\-.:]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
let match;
while ((match = attr_regex.exec(attr_str)) !== null) {
const key = match[1];
const value = match[2] !== undefined ? match[2] : (match[3] !== undefined ? match[3] : (match[4] !== undefined ? match[4] : ''));
if (func.runtime.ui.should_remove_attr(key, value)) {
continue;
}
const normalized_value = func.runtime.ui.normalize_attr_value(tag_name, key, value);
try {
el.setAttribute(key, normalized_value);
} catch (error) {
console.warn(`Failed to set attribute "${key}" on <${tag_name}>`, error);
}
}
}
return el;
};
func.runtime.ui.create_svg_element = function (element, prop, nodeP, $appendTo) {
const get_tag_str = function (element, prop, val) {
let attr_str = '';
for (const [key, value] of Object.entries(prop)) {
if (key.substr(0, 2) !== 'xu') {
if (func.runtime.ui.should_remove_attr(key, value)) {
continue;
}
const normalized_value = func.runtime.ui.normalize_attr_value(element, key, value);
attr_str += ` ${key}="${normalized_value}" `;
}
}
if (element === 'svg') {
return `<${element} ${attr_str} > `;
}
let ret = '';
if (val?.children?.length) {
ret = iterate_svg(val);
}
return `<${element} ${attr_str} > ${ret} </${element}>`;
};
const iterate_svg = function (node) {
let ret = '';
if (node.children) {
for (let val of node.children) {
if (val.type === 'comment') continue;
ret += get_tag_str(val.tagName, val.attributes, val);
}
}
return ret;
};
const svg_str = get_tag_str(element, prop);
const inner_str = iterate_svg(nodeP);
const full_svg_str = svg_str + inner_str + '</svg>';
// Parse the SVG string into a DOM element via a temporary container
const tmp = document.createElement('div');
tmp.innerHTML = full_svg_str;
const svg_el = tmp.firstElementChild;
return func.runtime.ui.append_to(svg_el, $appendTo);
};
func.runtime.ui.create_container_element = function (div_typeP, attr_str, prop, nodeP, $appendTo) {
const div = div_typeP || 'div';
if (div === 'svg') {
return func.runtime.ui.create_svg_element(div_typeP, prop, nodeP, $appendTo);
}
return func.runtime.ui.create_element(div, attr_str);
};
func.runtime.ui.find_hydration_candidate = function (options) {
if (!func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID])) {
return null;
}
if (!func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
return null;
}
if (options.is_placeholder || !options.treeP?.meta?.tree_id) {
return null;
}
const append_node = func.runtime.ui.get_first_node(options.$appendTo || options.$container);
if (!append_node) {
return null;
}
const children = func.runtime.ui.get_children(append_node);
for (let index = 0; index < children.length; index++) {
const child = children[index];
if (child?.__xuda_hydration_claimed) {
continue;
}
if (func.runtime.ui.get_attr(child, 'data-xuda-tree-id') !== `${options.treeP.meta.tree_id}`) {
continue;
}
child.__xuda_hydration_claimed = true;
func.runtime.ui.set_attr(child, 'data-xuda-client-activation', 'hydrate');
return child;
}
return null;
};
func.runtime.ui.build_xu_ui_id_seed = function (nodeP, dsSessionP, key_path, currentRecordId) {
const nodeId = nodeP.xu_tree_id || nodeP.id;
const elem_key = `${nodeId}-${key_path}-${currentRecordId}`;
return `${nodeP.id}-${elem_key}-${dsSessionP?.toString() || ''}`;
};
func.runtime.ui.build_container_key_path = function (container_xu_data, keyP, parent_infoP, nodeP, parent_nodeP) {
const key_segment = typeof keyP === 'undefined' || keyP === null ? '0' : `${keyP}`;
let key_path = `${container_xu_data?.key_path || '0'}-${key_segment}`;
const parent_identity = parent_nodeP?.xu_tree_id || parent_nodeP?.id;
const node_identity = nodeP?.xu_tree_id || nodeP?.id;
const is_iterated_clone = !!(parent_infoP?.iterate_info && parent_identity && node_identity && parent_identity === node_identity);
if (is_iterated_clone) {
key_path += '-iter';
}
return key_path;
};
func.runtime.ui.generate_xu_ui_id = async function (SESSION_ID, nodeP, $container, paramsP, keyP, precomputed = {}) {
const dsSessionP = paramsP.dsSessionP;
const _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
const containerXuData = precomputed.container_xu_data || func.runtime.ui.get_data($container)?.xuData;
let currentRecordId = typeof precomputed.currentRecordId !== 'undefined' ? precomputed.currentRecordId : containerXuData?.recordid || _ds?.currentRecordId || '';
// Same sentinel rule as create_container: an inherited 'newRecord' must not shadow the
// datasource's real positioned row (see create_container for the full failure mode).
if (currentRecordId === 'newRecord' && _ds?.currentRecordId && _ds.currentRecordId !== 'newRecord') {
const _live_rows = _ds.data_feed?.rows;
if (Array.isArray(_live_rows) && _live_rows.some((row) => row._ROWID === _ds.currentRecordId)) {
currentRecordId = _ds.currentRecordId;
}
}
const key_path = precomputed.key_path || func.runtime.ui.build_container_key_path(containerXuData, keyP, precomputed.parent_infoP, nodeP, precomputed.parent_nodeP);
const ui_id = func.runtime.ui.build_xu_ui_id_seed(nodeP, dsSessionP, key_path, currentRecordId);
if (func.runtime.ui.ui_id_hash_cache.has(ui_id)) {
return func.runtime.ui.ui_id_hash_cache.get(ui_id);
}
const hashed_ui_id = await func.common.fastHash(ui_id);
func.runtime.ui.ui_id_hash_cache.set(ui_id, hashed_ui_id);
return hashed_ui_id;
};
func.runtime.ui.create_container = async function (options) {
let _paramsP;
if (glb.XU_PERF) {
// paramsP carries callingDataSource_objP (full datasource) and screenInfo
// (full view doc); the JSON round-trip below serializes both per ELEMENT,
// which is quadratic on list size. The catch-path has always fallen back
// to this same shallow copy whenever paramsP is circular.
_paramsP = Object.assign({}, options.paramsP);
} else {
try {
_paramsP = JSON.parse(JSON.stringify(options.paramsP));
} catch (e) {
// paramsP may contain DOM element refs that create circular structures;
// fall back to a shallow copy which is sufficient for create_container.
_paramsP = Object.assign({}, options.paramsP);
}
}
const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[_paramsP.dsSessionP];
const $appendTo = func.runtime.ui.get_append_target(options.$container, options.$appendToP);
if (!$appendTo) return null;
const container_data = func.runtime.ui.get_data(options.$container);
const container_xu_data = container_data?.xuData;
const items = func.runtime.ui.get_node_child_items(options.nodeP);
let currentRecordId = container_xu_data?.recordid || (_ds ? _ds.currentRecordId : '');
// A 'newRecord' inherited from the container must not shadow a real positioned row of the
// child's own datasource. An array/JSON-datasource multi-view stamps its grid while the ds
// rests on the create-mode sentinel, then renders each row with a real currentRecordId; if
// the sentinel wins, every child gets a dead record binding — its click adopts 'newRecord'
// as the ds current record, row lookups fail and all further updates abort (game pager:
// page numbers dead, then arrows and reopen wedged).
if (currentRecordId === 'newRecord' && _ds?.currentRecordId && _ds.currentRecordId !== 'newRecord') {
const _live_rows = _ds.data_feed?.rows;
if (Array.isArray(_live_rows) && _live_rows.some((row) => row._ROWID === _ds.currentRecordId)) {
currentRecordId = _ds.currentRecordId;
}
}
try {
const key_path = func.runtime.ui.build_container_key_path(container_xu_data, options.keyP, options.parent_infoP, options.nodeP, options.parent_nodeP);
const elem_key = `${options.nodeP.xu_tree_id || options.nodeP.id}-${key_path}-${currentRecordId}`;
const hydration_candidate = func.runtime.ui.find_hydration_candidate({
...options,
$appendTo,
});
const $div = hydration_candidate || func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
const new_ui_id = await func.runtime.ui.generate_xu_ui_id(options.SESSION_ID, options.nodeP, options.$container, options.paramsP, options.keyP, {
container_xu_data,
currentRecordId,
key_path,
parent_infoP: options.parent_infoP,
parent_nodeP: options.parent_nodeP,
});
func.runtime.ui.apply_container_meta($div, {
ui_id: new_ui_id,
paramsP: _paramsP,
nodeP: options.nodeP,
currentRecordId,
keyP: options.keyP,
key_path,
$container: options.$container,
prop: options.prop,
elem_key,
elem_propP: options.elem_propP,
items,
parent_nodeP: options.parent_nodeP,
$root_container: options.$root_container,
parent_infoP: options.parent_infoP,
is_placeholder: options.is_placeholder,
classP: options.classP,
treeP: options.treeP,
});
if (!hydration_candidate && options.div_typeP !== 'svg') {
func.runtime.ui.append_to($div, $appendTo);
}
return $div;
} catch (e) {
console.error(e);
}
return null;
};
func.UI.utils = {};
func.UI.utils.indicator = {};
func.UI.utils.indicator.worker = {};
func.UI.utils.indicator.worker.busy = function () {
document.querySelectorAll('.progressLoader').forEach(function (el) { el.classList.add('progress_busy'); });
};
func.UI.utils.indicator.worker.normal = function () {
document.querySelectorAll('.progressLoader').forEach(function (el) { el.classList.remove('progress_busy'); });
};
func.UI.utils.indicator.server = {};
func.UI.utils.indicator.server.busy = function () {
func.UI.utils.indicator.worker.busy();
};
func.UI.utils.indicator.server.normal = function () {
func.UI.utils.indicator.worker.normal();
};
func.UI.utils.indicator.screen = {};
func.UI.utils.indicator.screen.busy = function () {
document.querySelectorAll('.progressLoader').forEach(function (el) { el.classList.add('progress_busy2'); });
};
func.UI.utils.indicator.screen.normal = function () {
document.querySelectorAll('.progressLoader').forEach(function (el) { el.classList.remove('progress_busy2'); });
};
func.UI.utils.save = function (SESSION_ID, stateP) {
if (stateP) func.UI.utils.indicator.worker.busy();
else func.UI.utils.indicator.worker.normal();
};
func.UI.utils.screen_blocker = function (onP, idP, dsP) {
if (!idP) {
func.utils.debug_report('', 'Worker', 'Missing reference id', 'E');
return;
}
window.oncontextmenu = function () {
if (onP) return false;
else return true;
};
if (!onP) {
delete SCREEN_BLOCKER_OBJ[idP];
// Screen just settled: immediately reconcile teleports (drop orphans /
// mirror host visibility) instead of waiting for the 1s maintenance tick.
// Cheap no-op when there are no teleports.
if (xu_isEmpty(SCREEN_BLOCKER_OBJ) && func.UI?.reconcile_teleports) {
try {
func.UI.reconcile_teleports();
} catch (e) {}
}
return;
}
if (idP !== 'Worker') {
SCREEN_BLOCKER_OBJ[idP] = Date.now();
}
};
// Browser runtime helpers moved to xuda_runtime.browser.js.
func.UI.utils.get_node_elm = function (SESSION_ID, dsSessionP, ui_idP, $container, is_app_panel, ui_type, functionP) {
if (!$container) {
$container = func.runtime.ui._wrap_matches([document.body]);
}
var $elm;
if (!is_app_panel && func.runtime?.ui?.find_xu_ui_in_root) {
$elm = func.runtime.ui.find_xu_ui_in_root(SESSION_ID, ui_idP);
} else if (is_app_panel && func.runtime?.ui?.find_panel_wrapper_in_root) {
$elm = func.runtime.ui.find_panel_wrapper_in_root(SESSION_ID, ui_idP);
}
if (!$elm?.length) {
$elm = func.UI.utils.find_in_element_data(is_app_panel ? 'xuPanelData' : 'xuData', $container, 'ui_id', ui_idP);
}
const _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSessionP];
if (_ds.tree_obj.renderType === 'form') {
return $elm;
}
var $grid_elm;
const grid_elements = $elm.toArray();
for (let index = 0; index < grid_elements.length; index++) {
const candidate = grid_elements[index];
if (func.runtime.ui.get_data(candidate)?.xuData?.recordid === _ds.currentRecordId) {
$grid_elm = func.runtime.ui._wrap_matches([candidate]);
break;
}
}
return $grid_elm;
return $elm;
};
func.UI.utils.get_nodeId = function (SESSION_ID, dsSessionP, ui_idP, $container, is_app_panel) {
var $elm = func.UI.utils.get_node_elm(SESSION_ID, dsSessionP, ui_idP, $container, is_app_panel, null, null);
if (is_app_panel) {
return func.runtime.ui.get_data($elm)?.xuData?.panel_info?.prop?.id;
} else {
return func.runtime.ui.get_attr($elm, 'nodeId');
}
};
// func.UI.utils.get_element_info = function (SESSION_ID, dsSessionP, ui_idP, $container, is_app_panel, ui_type, functionP) {
// var $elm = func.UI.utils.get_node_elm(SESSION_ID, dsSessionP, ui_idP, $container, is_app_panel, ui_type, functionP);
// var ret = {};
// if ($elm?.length) {
// //length added 20210209
// // if (is_app_panel) {
// // if ($elm?.data()?.xuData.panel_info) {
// // ret.nodeId = $elm.data().xuData.panel_info.prop.id;
// // } else {
// // ret.nodeId = $elm.data().xuData.properties.id;
// // }
// // } else {
// // ret.nodeId = $elm.data().xuData.nodeId;
// ret.nodeId = $elm.data().xuData.xu_id;
// // ret.nodeId = $elm.attr('xu-ui-id');
// // }
// }
// ret.$elm = $elm;
// return ret;
// };
func.UI.utils.get_ui_id_count = function (SESSION_ID, dsSessionP, ui_idP, $container) {
return func.UI.utils.get_node_elm(SESSION_ID, dsSessionP, ui_idP, $container, null, null).length;
};
// func.UI.utils.get_ui_info_tree_scope = function (SESSION_ID, dsP, ui_id, is_app_panel, ui_type, $container, functionP) {
// var drill_parent = function () {
// if (SESSION_OBJ[SESSION_ID].DS_GLB[dsP].parentDataSourceNo) {
// return func.UI.utils.get_ui_info_tree_scope(SESSION_ID, SESSION_OBJ[SESSION_ID].DS_GLB[dsP].parentDataSourceNo, ui_id, is_app_panel, ui_type);
// } else if (ui_type === 'xu-app-page') {
// if (elm_info.$elm.length) {
// return {
// ds: dsP,
// id: id,
// $elm: elm_info.$elm,
// };
// }
// }
// };
// if (!SESSION_OBJ[SESSION_ID].DS_GLB[dsP]) return;
// var elm_info = func.UI.utils.get_element_info(SESSION_ID, dsP, ui_id, $container, is_app_panel, ui_type, functionP);
// var id = elm_info.nodeId;
// if (!id) {
// return drill_parent();
// } else {
// if (is_app_panel) {
// return {
// ds: func.UI.utils.get_node_elm(SESSION_ID, dsP, ui_id, $container, is_app_panel).data().xuData.paramsP.dsSessionP,
// id: id,
// $elm: elm_info.$elm,
// };
// } else {
// return {
// ds: dsP,
// id: id,
// $elm: elm_info.$elm,
// };
// }
// }
// };
func.UI.utils.clean_node_busy = function (node) {
var run_node = function (node) {
node.busy = false;
for (var key in node.children) {
run_node(node.children[key]);
}
};
run_node(node);
return node;
};
// func.UI.utils.get_ui_info_tree_scope_sync = function (SESSION_ID, dsP, ui_id, ui_type, callback, $container, functionP) {
// var ret;
// var is_app_panel = false;
// if (ui_type === 'xu-panel') {
// is_app_panel = true;
// }
// ret = func.UI.utils.get_ui_info_tree_scope(SESSION_ID, dsP, ui_id, is_app_panel, ui_type, $container, functionP);
// if (!ret) {
// var attempts = 0;
// const run = function () {
// ret = func.UI.utils.get_ui_info_tree_scope(SESSION_ID, dsP, ui_id, is_app_panel, ui_type, $container, functionP);
// if (ret || attempts > 10) {
// callback(ret);
// } else {
// attempts++;
// console.log(attempts, dsP, ui_id, is_app_panel);
// setTimeout(function () {
// run();
// }, 100);
// }
// };
// run();
// } else {
// callback(ret);
// }
// };
func.UI.utils.live_preview_element_inspect_on = function (SESSION_ID, service) {
var elements = document.querySelectorAll('[xu-ui-id]');
elements.forEach(function (val) {
var _mouseenter_handler = function () {
document.querySelectorAll('.preview_mark').forEach(function (el) {
el.classList.remove('preview_mark');
el.removeEventListener('click', el._live_preview_click_handler);
});
val.classList.add('preview_mark');
var _click_handler = function (e) {
const _session = SESSION_OBJ[SESSION_ID];
console.log(func.runtime.ui.get_data(this));
const data = func.runtime.ui.get_data(this);
const obj = {
service: service + '_result',
id: STUDIO_WEBSOCKET_CONNECTION_ID,
uid: _session.USR_OBJ._id,
source: 'runtime',
app_id: _session.app_id,
gtp_token: _session.gtp_token,
session_id: SESSION_ID,
app_token: _session.app_token,
};
switch (service) {
case 'live_preview_element_reference':
obj.data = { prog_id: data.xuData.prog_id, node: data.xuData.node };
break;
case 'live_preview_element_info':
obj.data = {
element_info: data.debug_info,
datasource: _session.DS_GLB[data.paramsP.dsSessionP],
};
break;
case 'live_preview_element_dnd':
// code block
break;
default:
// code block
}
STUDIO_PEER_CONN_SEND_METHOD(obj);
// STUDIO_WEBSOCKET.emit("message", obj);
func.UI.utils.live_preview_element_inspect_off();
if (typeof LIVE_PREVIEW_APP_ACTIVE !== 'undefined') {
var refBtn = document.getElementById('live_preview_element_reference_btn');
if (refBtn) refBtn.style.color = 'unset';
var infoBtn = document.getElementById('live_preview_element_info_btn');
if (infoBtn) infoBtn.style.color = 'unset';
}
};
val._live_preview_click_handler = _click_handler;
val.addEventListener('click', _click_handler);
};
var _mouseleave_handler = function () {
val.classList.remove('preview_mark');
if (val._live_preview_click_handler) {
val.removeEventListener('click', val._live_preview_click_handler);
delete val._live_preview_click_handler;
}
};
val._live_preview_mouseenter_handler = _mouseenter_handler;
val._live_preview_mouseleave_handler = _mouseleave_handler;
val.addEventListener('mouseenter', _mouseenter_handler);
val.addEventListener('mouseleave', _mouseleave_handler);
});
};
func.UI.utils.live_preview_element_inspect_off = function () {
document.querySelectorAll('[xu-ui-id]').forEach(function (val) {
if (val._live_preview_mouseenter_handler) {
val.removeEventListener('mouseenter', val._live_preview_mouseenter_handler);
delete val._live_preview_mouseenter_handler;
}
if (val._live_preview_mouseleave_handler) {
val.removeEventListener('mouseleave', val._live_preview_mouseleave_handler);
delete val._live_preview_mouseleave_handler;
}
});
document.querySelectorAll('.preview_mark').forEach(function (el) {
el.classList.remove('preview_mark');
if (el._live_preview_click_handler) {
el.removeEventListener('click', el._live_preview_click_handler);
delete el._live_preview_click_handler;
}
});
};
func.UI.utils.live_preview_show_selected_element = function (nodeid) {
document.querySelectorAll('.preview_mark').forEach(function (el) { el.classList.remove('preview_mark'); });
document.querySelectorAll(`[nodeid="${nodeid}"]`).forEach(function (el) { el.classList.add('preview_mark'); });
};
func.UI.utils.get_url_attribute = function (SESSION_ID, key) {
const _session = SESSION_OBJ[SESSION_ID];
const platform = func.runtime.platform;
const url_param = glb.URL_PARAMS?.get?.(key) || (key === 'app_id' ? glb.URL_PARAMS?.get?.('id') : null);
const root_attribute = func.runtime.ui.get_attr(_session.root_element, key);
const option_param = _session.opt?.params?.[key];
const option_value = _session.opt?.[key];
const cookie_value = platform.get_cookie_item(key);
const storage_value = platform.get_storage_item(key, 'local');
return url_param || root_attribute || option_param || option_value || cookie_value || storage_value;
};
func.UI.utils.get_root_element_attributes = function (SESSION_ID) {
// test2345
var ret = {};
var root_el = func.runtime.ui.get_first_node(SESSION_OBJ[SESSION_ID].root_element);
if (root_el) {
var attrs = root_el.attributes;
for (var i = 0; i < attrs.length; i++) {
// attributes is not a plain object, but an array
// of attribute nodes, which contain both the name and value
if (attrs[i].specified) {
ret[attrs[i].name] = attrs[i].value;
}
}
}
return ret;
};
func.UI.utils.prompt_confirm_window = async function (SESSION_ID, messageP, picP, confirmYesFuncP, confirmNoFuncP, confirmCancelFuncP, title, colorP) {
if (confirmNoFuncP) {
buttons.push({
text: 'Disagree',
role: 'no',
cssClass: 'secondary',
handler: () => {
confirmNoFuncP();
},
});
}
if (confirmYesFuncP) {
buttons.push({
text: 'Agree',
role: 'yes',
cssClass: 'secondary',
handler: () => {
confirmYesFuncP();
},
});
}
if (confirmCancelFuncP) {
buttons.push({
text: 'Dismiss',
role: 'cancel',
cssClass: 'secondary',
handler: () => {
confirmCancelFuncP();
},
});
}
UI_FRAMEWORK_PLUGIN.modal(title, messageP, buttons);
};
func.UI.utils.alert = function (msg) {
console.error(msg);
};
func.UI.utils.progressScreen = {};
func.UI.utils.progressScreen.show = function (SESSION_ID, textP, show_bytesP, error, progress_off, logo_off) {
if (glb.IS_WORKER) {
return;
}
const app_obj = APP_OBJ?.[SESSION_OBJ?.[SESSION_ID]?.app_id];
var background_color = '';
var icon = app_obj?._conf?.logo_url || 'https://xuda.ai/dist/images/xuda_logo.png';
if (app_obj?.app_pic) {
icon = app_obj.app_pic;
}
if (app_obj && app_obj.app_icon_prop) {
if (app_obj.app_icon_prop.app_icon_background_color) {
background_color = app_obj.app_icon_prop.app_icon_background_color;
}
// if (app_obj.app_type === "master_team" && app_obj.app_pic) {
// let app_id = APP_OBJ[SESSION_OBJ[SESSION_ID].app_id];
// if (app_obj.app_replicate) {
// app_id = app_obj.app_replicate;
// }
// }
}
IS_PROGRESS_SCREEN_OPEN = true;
var progressScreen = document.getElementById('progressScreen2');
if (progressScreen) progressScreen.innerHTML = '';
document.querySelectorAll('.loader').forEach(function (el) { el.style.display = 'none'; });
if (background_color && progressScreen) {
progressScreen.style.color = func.common.getContrast_color(background_color);
progressScreen.style.backgroundColor = background_color;
}
setTimeout(function () {
if (IS_PROGRESS_SCREEN_OPEN) {
var error_msg = `<h4>${textP}</h4>`;
// $("#progressScreen2_text").fadeIn(500, function () {
// if (!logo_off) {
// $(this).prepend(`<img class='progressScreen_logo' src='${icon}'>`);
// }
// $(this).append(
// error
// ? error_msg
// : typeof textP === "object"
// ? textP
// : `<p>${textP}</p>`
// );
// });
var progressText = document.getElementById('progressScreen2_text');
if (progressText) {
progressText.style.display = '';
if (!logo_off) {
var img = document.createElement('img');
img.className = 'progressScreen_logo';
img.src = icon;
progressText.prepend(img);
}
if (error) {
progressText.insertAdjacentHTML('beforeend', error_msg);
} else if (typeof textP === 'object') {
progressText.appendChild(textP);
} else {
progressText.insertAdjacentHTML('beforeend', `<p>${textP}</p>`);
}
}
}
}, 500);
if (progressScreen) progressScreen.style.display = '';
var textDiv = document.createElement('div');
textDiv.id = 'progressScreen2_text';
textDiv.style.display = 'none';
if (progressScreen) progressScreen.appendChild(textDiv);
// .hide()
// .fadeIn("slow"); //.append(textP)
};
func.UI.utils.progressScreen.hide = function () {
if (glb.IS_WORKER) {
return;
}
IS_PROGRESS_SCREEN_OPEN = false;
// window.clearInterval(PROGRESS_INTERVAL);
// setTimeout(function () {
var ps2 = document.getElementById('progressScreen2');
if (ps2) ps2.style.display = 'none';
var ps2text = document.getElementById('progressScreen2_text');
if (ps2text) ps2text.innerHTML = '';
// $("#progressScreen2").css({
// height: "0px",
// top: "-100px",
// "background-image": "none"
// });
};
func.UI.utils.find_in_element_data = function (folder, $elm, key, val) {
if (!$elm?.length) {
return func.runtime.ui._wrap_matches([]);
}
if (folder === 'xuData' && key === 'ui_id' && typeof val !== 'undefined') {
return func.runtime.ui.find_by_selector($elm, `[xu-ui-id="${val}"]`);
}
if (folder === 'xuPanelWrapper' && key === 'isWrapper' && typeof val === 'undefined') {
return func.runtime.ui.find_by_selector($elm, '[xu-panel-wrapper-id]');
}
let candidate_selector;
if (folder === 'xuData' || folder === 'xuPanelData') {
candidate_selector = '[xu-ui-id]';
} else if (folder === 'xuPanelWrapper') {
candidate_selector = '[xu-panel-wrapper-id]';
} else {
candidate_selector = '*';
}
const matches = [];
const roots = $elm.toArray();
for (let root_index = 0; root_index < roots.length; root_index++) {
const root_node = roots[root_index];
const candidates = root_node?.querySelectorAll?.(candidate_selector) || [];
for (let candidate_index = 0; candidate_index < candidates.length; candidate_index++) {
const candidate = candidates[candidate_index];
const folder_data = func.runtime.ui.get_data(candidate)?.[folder];
if (!folder_data) {
continue;
}
if (typeof val === 'undefined') {
if (!folder_data[key]) {
continue;
}
} else if (folder_data[key] != val) {
continue;
}
matches.push(candidate);
}
}
return func.runtime.ui._wrap_matches(matches);
};
func.UI.utils.init_ui_framework = async function (SESSION_ID, prog_id) {
if (typeof glb.SLIM_BUNDLE !== 'undefined' || glb.SLIM_BUNDLE) return;
var _session = SESSION_OBJ[SESSION_ID];
var tree_obj = await func.utils.TREE_OBJ.get(SESSION_ID, prog_id);
if (xu_isEmpty(tree_obj)) {
return console.error('Error: prog ' + prog_id + ' not found.');
}
const report_error = function (descP, warn) {
func.utils.debug.log(SESSION_ID, prog_id, {
module: 'component',
action: 'Init',
source: 'install ui framework',
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'component',
prog_id: prog_id,
});
};
const plugin_name = tree_obj.uiFramework;
if (!plugin_name) {
return report_error('no frameworks plugin defined', true);
}
if (plugin_name === UI_FRAMEWORK_INSTALLED) return;
const _plugin = APP_OBJ[_session.app_id]?.app_plugins_purchased?.[plugin_name];
if (!_plugin?.installed) {
return report_error(`plugin ${plugin_name} not installed`);
}
var _ui_framework_index;
try {
const runtime_mjs = `${_plugin.manifest['runtime.mjs'].dist ? 'dist/' : ''}runtime.mjs`;
_ui_framework_index = await func.utils.get_plugin_resource(SESSION_ID, plugin_name, runtime_mjs);
} catch (error) {
return report_error(`plugin ${plugin_name} not found`);
}
if (!_ui_framework_index) {
return report_error(`plugin ${plugin_name} is empty`);
}
if (!_ui_framework_index.core) {
return report_error(`plugin core not found in ${plugin_name} `);
}
// if (!_ui_framework_index.resources) {
// return report_error(`plugin resources not found in ${plugin_name} `);
// }
var _ui_framework_dashboard_setup;
var _ui_framework_dashboard_setup_data_ret;
const ui_framework_core = new _ui_framework_index.core();
if (ui_framework_core.init) {
try {
const setup_mjs = `${_plugin.manifest['index.mjs'].dist ? 'dist/' : ''}index.mjs`;
_ui_framework_dashboard_setup = await func.utils.get_plugin_resource(SESSION_ID, plugin_name, setup_mjs);
if (_ui_framework_dashboard_setup) {
_ui_framework_dashboard_setup_data_ret = await func.utils.get_plugin_setup(SESSION_ID, plugin_name);
}
} catch (error) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_GUI_020',
source: 'get_plugin_setup',
message: `plugin setup import failed for ${plugin_name}`,
type: 'W',
err: error,
details: {
plugin_name,
},
});
}
}
if (plugin_name !== UI_FRAMEWORK_INSTALLED) {
UI_FRAMEWORK_INSTALLED = plugin_name;
const _plugin_INSTALLED = APP_OBJ[_session.app_id]?.app_plugins_purchased?.[UI_FRAMEWORK_INSTALLED];
UI_FRAMEWORK_PLUGIN = _ui_framework_index;
if (UI_FRAMEWORK_INSTALLED) {
var _current_ui_framework_runtime;
try {
const _runtime_mjs = `${_plugin_INSTALLED.manifest['runtime.mjs'].dist ? 'dist/' : ''}runtime.mjs`;
_current_ui_framework_runtime = await func.utils.get_plugin_resource(SESSION_ID, UI_FRAMEWORK_INSTALLED, _runtime_mjs);
} catch (error) {
return report_error(`plugin ${UI_FRAMEWORK_INSTALLED} not found`);
}
// remove
if (UI_FRAMEWORK_PLUGIN.core.discard && UI_FRAMEWORK_PLUGIN.init_id) {
UI_FRAMEWORK_PLUGIN.core.discard(UI_FRAMEWORK_PLUGIN.init_id);
}
if (_ui_framework_index.resources) {
for (var key in _current_ui_framework_runtime.resources) {
var val = _current_ui_framework_runtime.resources[key];
func.utils.remove_js_css_file(val.url, val.type);
}
}
}
// install
var init_id;
if (_ui_framework_index.resources) {
for await (var val of _ui_framework_index.resources) {
var ret = undefined;
var url = val.url;
switch (val.type) {
case 'module':
try {
// ret = await load_module(url);
ret = await func.utils.load_js_on_demand(url, 'module');
} catch (error) {
console.error('[xuda-runtime] caught xuda_UI.utils.js:630:', error);
}
break;
case 'css':
ret = func.utils.load_css_on_demand(url);
break;
case 'js':
ret = await func.utils.load_js_on_demand(url);
break;
default:
break;
}
if (val.callback) {
val.callback(ret);
}
}
}
if (ui_framework_core.init) {
init_id = await ui_framework_core.init(_ui_framework_dashboard_setup_data_ret?.code > -1 && _ui_framework_dashboard_setup_data_ret.data);
UI_FRAMEWORK_PLUGIN.init_id = init_id;
}
}
};
// old
// func.UI.utils.get_panels_from_dom = function (SESSION_ID, ignore_disableAutoRefresh) {
// const $elm = func.UI.utils.find_in_element_data('xuPanelData', $(SESSION_OBJ[SESSION_ID].root_element), 'parent_element_ui_id');
// var panels_obj = {};
// // set panels_obj
// for (const [elem_key, elem_val] of Object.entries($elm)) {
// if (elem_key === 'length') break;
// var $div = $(elem_val);
// let xuData = $div.data().xuData;
// if (!$div.data().xuPanelData) continue;
// let dsSession = xuData.paramsP.dsSessionP;
// var _session = SESSION_OBJ[SESSION_ID];
// let _ds = _session?.DS_GLB[dsSession];
// if (!_ds) continue;
// if (!ignore_disableAutoRefresh && _ds.tree_obj.disableAutoRefresh) {
// continue;
// }
// const parent_element_ui_id = $div.data().xuPanelData.parent_element_ui_id;
// if (!panels_obj[parent_element_ui_id]) {
// panels_obj[parent_element_ui_id] = {
// _ds,
// $div,
// ids: [],
// };
// }
// panels_obj[parent_element_ui_id].ids.push($div.attr('xu-ui-id'));
// }
// return panels_obj;
// };
func.UI.utils.get_panels_wrapper_from_dom = async function (SESSION_ID, $xu_embed_container, ignore_disableAutoRefresh) {
const get_refresh_state = function () {
return func.runtime?.ui?.ensure_refresh_dependency_state?.(SESSION_ID) || null;
};
const get_cached_panels_obj = function (refresh_state) {
const cached_panels = ignore_disableAutoRefresh ? refresh_state?.panel_wrappers_cache : refresh_state?.panel_wrappers_active_cache;
const panels_obj = cached_panels || {};
let requires_rebuild = false;
const panel_keys = Object.keys(refresh_state?.panel_wrappers_cache || {});
for (let index = 0; index < panel_keys.length; index++) {
const panel_entry = refresh_state.panel_wrappers_cache[panel_keys[index]];
if (!panel_entry?.$panel_div?.length || !panel_entry.$panel_div[0]?.isConnected) {
requires_rebuild = true;
break;
}
}
if (requires_rebuild) {
refresh_state.panel_wrappers_dirty = true;
refresh_state.panel_wrappers_cache = {};
refresh_state.panel_wrappers_active_cache = {};
return null;
}
return panels_obj;
};
const refresh_state = get_refresh_state();
if (refresh_state && !refresh_state.panel_wrappers_dirty && !xu_isEmpty(refresh_state.panel_wrappers_cache)) {
const cached_panels_obj = get_cached_panels_obj(refresh_state);
if (cached_panels_obj) {
return cached_panels_obj;
}
}
let $elm = func.runtime?.ui?.get_refresh_indexed_panel_wrappers?.(SESSION_ID);
if (!$elm?.length) {
const $root = func.runtime?.ui?.get_refresh_index_root?.(SESSION_ID) || func.runtime.ui._wrap_matches([func.runtime.ui.get_first_node(SESSION_OBJ[SESSION_ID].root_element)]);
if (func.runtime?.ui?.find_refresh_elements_by_attr) {
$elm = func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-panel-wrapper-id');
} else {
var _root_node = func.runtime.ui.get_first_node($root);
var _panel_matches = [];
if (_root_node?.matches?.('[xu-panel-wrapper-id]')) _panel_matches.push(_root_node);
_root_node?.querySelectorAll?.('[xu-panel-wrapper-id]')?.forEach(function (el) { _panel_matches.push(el); });
$elm = func.runtime.ui._wrap_matches(_panel_matches);
}
}
const panels_obj = {};
const prog_doc_cache = {};
// set panels_obj
const panel_elements = Array.isArray($elm) ? $elm : ($elm.toArray ? $elm.toArray() : Array.from($elm));
for (let elem_index = 0; elem_index < panel_elements.length; elem_index++) {
const $panel_div = func.runtime.ui._wrap_matches([panel_elements[elem_index]]);
const panel_wrapper_data = func.runtime.ui.get_data($panel_div)?.xuPanelWrapper;
const panelXuAttributes = panel_wrapper_data?.panelXuAttributes;
const panelDivData = panel_wrapper_data?.panelDivData;
if (!panelXuAttributes) continue; // skip if no longer in dom
const xu_ui_id = func.runtime.ui.get_attr($panel_div, 'xu-ui-id');
if (!panels_obj[xu_ui_id]) {
var _session = SESSION_OBJ[SESSION_ID];
let _ds = _session?.DS_GLB[panelDivData.xuData.paramsP.dsSessionP];
if (!_ds) continue;
let prog_doc = prog_doc_cache[_ds.prog_id];
if (!prog_doc) {
prog_doc = await func.utils.DOCS_OBJ.get(SESSION_ID, _ds.prog_id);
prog_doc_cache[_ds.prog_id] = prog_doc;
}
if (!ignore_disableAutoRefresh && prog_doc.properties.disableAutoRefresh) {
continue;
}
const ids = [];
const child_nodes = $panel_div[0]?.children || [];
for (let child_index = 0; child_index < child_nodes.length; child_index++) {
const child_node = child_nodes[child_index];
if (func.runtime.ui.get_data(child_node)?.xuPanelData) {
ids.push(func.runtime.ui.get_attr(child_node, 'xu-ui-id'));
}
}
panels_obj[xu_ui_id] = {
panelXuAttributes,
progUi: prog_doc.progUi,
prog_doc,
$panel_div,
_ds,
ids,
};
}
}
if (refresh_state) {
refresh_state.panel_wrappers_cache = panels_obj;
refresh_state.panel_wrappers_active_cache = {};
const panel_keys = Object.keys(panels_obj);
for (let index = 0; index < panel_keys.length; index++) {
const xu_ui_id = panel_keys[index];
const panel_entry = panels_obj[xu_ui_id];
if (panel_entry?.prog_doc?.properties?.disableAutoRefresh) {
continue;
}
refresh_state.panel_wrappers_active_cache[xu_ui_id] = panel_entry;
}
refresh_state.panel_wrappers_dirty = false;
}
if (!ignore_disableAutoRefresh) {
return refresh_state?.panel_wrappers_active_cache || panels_obj;
}
return panels_obj;
};
func.UI.worker = {};
func.UI.worker.ID = null;
func.UI.worker.idle = 0;
func.UI.worker.ensure_runtime_indexes = function () {
if (!UI_WORKER_OBJ.job_index_by_num) {
UI_WORKER_OBJ.job_index_by_num = {};
}
if (!UI_WORKER_OBJ.jobs_by_queue_key) {
UI_WORKER_OBJ.jobs_by_queue_key = {};
}
if (!UI_WORKER_OBJ.viewport_height_set_ids_set) {
UI_WORKER_OBJ.viewport_height_set_ids_set = new Set(UI_WORKER_OBJ.viewport_height_set_ids || []);
}
if (!UI_WORKER_OBJ.pending_delete_sessions) {
UI_WORKER_OBJ.pending_delete_sessions = new Set();
}
if (!UI_WORKER_OBJ.pending_delete_elements_by_session) {
UI_WORKER_OBJ.pending_delete_elements_by_session = {};
}
if (typeof UI_WORKER_OBJ.run_timer === 'undefined') {
UI_WORKER_OBJ.run_timer = null;
}
if (typeof UI_WORKER_OBJ.run_in_progress === 'undefined') {
UI_WORKER_OBJ.run_in_progress = false;
}
if (typeof UI_WORKER_OBJ.run_again === 'undefined') {
UI_WORKER_OBJ.run_again = false;
}
if (typeof UI_WORKER_OBJ.run_schedule_type === 'undefined') {
UI_WORKER_OBJ.run_schedule_type = null;
}
if (typeof UI_WORKER_OBJ.dom_jobs_per_frame === 'undefined') {
UI_WORKER_OBJ.dom_jobs_per_frame = 8;
}
if (!UI_WORKER_OBJ.job_lane_counts) {
UI_WORKER_OBJ.job_lane_counts = { data: 0, dom: 0 };
(UI_WORKER_OBJ.jobs || []).forEach(function (job) {
if (!job) {
return;
}
const lane = job.lane || func.UI.worker.get_job_lane(job.functionP);
UI_WORKER_OBJ.job_lane_counts[lane] = (UI_WORKER_OBJ.job_lane_counts[lane] || 0) + 1;
});
}
if (typeof UI_WORKER_OBJ.active_jobs_count === 'undefined') {
UI_WORKER_OBJ.active_jobs_count = 0;
for (let index = 0; index < (UI_WORKER_OBJ.jobs || []).length; index++) {
if (UI_WORKER_OBJ.jobs[index]) {
UI_WORKER_OBJ.active_jobs_count++;
}
}
}
if (typeof UI_WORKER_OBJ.job_holes_count === 'undefined') {
UI_WORKER_OBJ.job_holes_count = Math.max(0, (UI_WORKER_OBJ.jobs || []).length - UI_WORKER_OBJ.active_jobs_count);
}
if (typeof UI_WORKER_OBJ.first_active_job_index === 'undefined') {
UI_WORKER_OBJ.first_active_job_index = null;
for (let index = 0; index < (UI_WORKER_OBJ.jobs || []).length; index++) {
if (UI_WORKER_OBJ.jobs[index]) {
UI_WORKER_OBJ.first_active_job_index = index;
break;
}
}
}
};
func.UI.worker.get_job_lane = function (functionP) {
if (functionP === 'update_datasource') {
return 'data';
}
return 'dom';
};
func.UI.worker.register_job = function (job, index = UI_WORKER_OBJ.jobs.length - 1) {
func.UI.worker.ensure_runtime_indexes();
if (!job) {
return false;
}
if (!job.lane) {
job.lane = func.UI.worker.get_job_lane(job.functionP);
}
UI_WORKER_OBJ.job_index_by_num[job.job_num] = index;
if (job.queue_key) {
UI_WORKER_OBJ.jobs_by_queue_key[job.queue_key] = job;
}
UI_WORKER_OBJ.job_lane_counts[job.lane] = (UI_WORKER_OBJ.job_lane_counts[job.lane] || 0) + 1;
UI_WORKER_OBJ.active_jobs_count++;
if (UI_WORKER_OBJ.first_active_job_index === null || index < UI_WORKER_OBJ.first_active_job_index) {
UI_WORKER_OBJ.first_active_job_index = index;
}
return true;
};
func.UI.worker.unregister_job = function (job) {
func.UI.worker.ensure_runtime_indexes();
if (!job) {
return false;
}
delete UI_WORKER_OBJ.job_index_by_num[job.job_num];
if (job.queue_key && UI_WORKER_OBJ.jobs_by_queue_key[job.queue_key]?.job_num === job.job_num) {
delete UI_WORKER_OBJ.jobs_by_queue_key[job.queue_key];
}
if (job.lane && UI_WORKER_OBJ.job_lane_counts[job.lane] > 0) {
UI_WORKER_OBJ.job_lane_counts[job.lane]--;
}
if (UI_WORKER_OBJ.active_jobs_count > 0) {
UI_WORKER_OBJ.active_jobs_count--;
}
return true;
};
func.UI.worker.advance_first_active_job_index = function (start_index = UI_WORKER_OBJ.first_active_job_index || 0) {
func.UI.worker.ensure_runtime_indexes();
if (!UI_WORKER_OBJ.active_jobs_count) {
UI_WORKER_OBJ.first_active_job_index = null;
return null;
}
for (let index = Math.max(0, start_index); index < UI_WORKER_OBJ.jobs.length; index++) {
if (UI_WORKER_OBJ.jobs[index]) {
UI_WORKER_OBJ.first_active_job_index = index;
return UI_WORKER_OBJ.jobs[index];
}
}
UI_WORKER_OBJ.first_active_job_index = null;
return null;
};
func.UI.worker.compact_jobs = function () {
func.UI.worker.ensure_runtime_indexes();
if (!UI_WORKER_OBJ.job_holes_count) {
return false;
}
const compacted_jobs = [];
UI_WORKER_OBJ.job_index_by_num = {};
UI_WORKER_OBJ.jobs_by_queue_key = {};
UI_WORKER_OBJ.job_lane_counts = { data: 0, dom: 0 };
UI_WORKER_OBJ.job_holes_count = 0;
UI_WORKER_OBJ.first_active_job_index = null;
for (let index = 0; index < UI_WORKER_OBJ.jobs.length; index++) {
const job = UI_WORKER_OBJ.jobs[index];
if (!job) {
continue;
}
const compacted_index = compacted_jobs.length;
compacted_jobs.push(job);
UI_WORKER_OBJ.job_index_by_num[job.job_num] = compacted_index;
if (job.queue_key) {
UI_WORKER_OBJ.jobs_by_queue_key[job.queue_key] = job;
}
UI_WORKER_OBJ.job_lane_counts[job.lane] = (UI_WORKER_OBJ.job_lane_counts[job.lane] || 0) + 1;
if (UI_WORKER_OBJ.first_active_job_index === null) {
UI_WORKER_OBJ.first_active_job_index = compacted_index;
}
}
UI_WORKER_OBJ.jobs = compacted_jobs;
return true;
};
func.UI.worker.maybe_compact_jobs = function (force = false) {
func.UI.worker.ensure_runtime_indexes();
if (!UI_WORKER_OBJ.job_holes_count) {
return false;
}
const jobs_length = UI_WORKER_OBJ.jobs.length;
// XU_PERF: compacting after every front deletion is O(queue²) on large
// refreshes; front holes are already skipped via first_active_job_index and
// the index maps are maintained incrementally, so "force" is only a hint
const should_compact = glb.XU_PERF
? UI_WORKER_OBJ.job_holes_count >= Math.max(1024, Math.ceil(jobs_length / 2))
: force ||
UI_WORKER_OBJ.job_holes_count >= 50 ||
UI_WORKER_OBJ.job_holes_count >= Math.ceil(jobs_length / 3) ||
(UI_WORKER_OBJ.first_active_job_index !== null && UI_WORKER_OBJ.first_active_job_index > 0);
if (!should_compact) {
return false;
}
return func.UI.worker.compact_jobs();
};
func.UI.worker.get_first_active_job = function () {
func.UI.worker.ensure_runtime_indexes();
if (!UI_WORKER_OBJ.active_jobs_count) {
UI_WORKER_OBJ.first_active_job_index = null;
return null;
}
if (UI_WORKER_OBJ.first_active_job_index !== null) {
const active_job = UI_WORKER_OBJ.jobs[UI_WORKER_OBJ.first_active_job_index];
if (active_job) {
return active_job;
}
}
const job = func.UI.worker.advance_first_active_job_index(0);
if (job) {
if (UI_WORKER_OBJ.first_active_job_index > 0) {
func.UI.worker.maybe_compact_jobs(true);
}
return job;
}
UI_WORKER_OBJ.active_jobs_count = 0;
UI_WORKER_OBJ.job_holes_count = UI_WORKER_OBJ.jobs.length;
UI_WORKER_OBJ.first_active_job_index = null;
func.UI.worker.maybe_compact_jobs(true);
return null;
};
func.UI.worker.get_pending_run_mode = function () {
func.UI.worker.ensure_runtime_indexes();
if (!UI_WORKER_OBJ.active_jobs_count) {
return null;
}
if (UI_WORKER_OBJ.job_lane_counts?.data > 0) {
return 'data';
}
return 'dom';
};
func.UI.worker.cancel_pending_run = function () {
func.UI.worker.ensure_runtime_indexes();
if (!UI_WORKER_OBJ.run_timer) {
return false;
}
if (UI_WORKER_OBJ.run_schedule_type === 'dom_frame') {
if (typeof window !== 'undefined' && typeof window.cancelAnimationFrame === 'function') {
window.cancelAnimationFrame(UI_WORKER_OBJ.run_timer);
} else if (typeof cancelAnimationFrame === 'function') {
cancelAnimationFrame(UI_WORKER_OBJ.run_timer);
} else {
clearTimeout(UI_WORKER_OBJ.run_timer);
}
} else {
clearTimeout(UI_WORKER_OBJ.run_timer);
}
UI_WORKER_OBJ.run_timer = null;
UI_WORKER_OBJ.run_schedule_type = null;
return true;
};
func.UI.worker.request_frame = function (callback) {
if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
return window.requestAnimationFrame(callback);
}
if (typeof requestAnimationFrame === 'function') {
return requestAnimationFrame(callback);
}
return setTimeout(callback, 16);
};
func.UI.worker.reindex_jobs_from = function (start_index = 0) {
func.UI.worker.ensure_runtime_indexes();
for (let index = Math.max(0, start_index); index < UI_WORKER_OBJ.jobs.length; index++) {
const job = UI_WORKER_OBJ.jobs[index];
if (!job) {
continue;
}
UI_WORKER_OBJ.job_index_by_num[job.job_num] = index;
if (job.queue_key) {
UI_WORKER_OBJ.jobs_by_queue_key[job.queue_key] = job;
}
}
};
func.UI.worker.get_session_root = function (SESSION_ID) {
if (func.runtime?.ui?.get_root_element) {
const $root = func.runtime.ui.get_root_element(SESSION_ID);
if ($root?.length) {
return $root;
}
}
return func.runtime.ui._wrap_matches([document.body]);
};
func.UI.worker.get_session_runtime_elements = function (SESSION_ID) {
if (func.runtime?.ui?.get_refresh_indexed_runtime_elements) {
const $indexed = func.runtime.ui.get_refresh_indexed_runtime_elements(SESSION_ID);
if ($indexed?.length) {
return $indexed;
}
}
const $root = func.UI.worker.get_session_root(SESSION_ID);
if (func.runtime?.ui?.get_refresh_index_elements) {
return func.runtime.ui.get_refresh_index_elements(SESSION_ID, $root);
}
var _rt_root_node = func.runtime.ui.get_first_node($root);
var _rt_matches = [];
if (_rt_root_node?.matches?.('[xu-ui-id]')) _rt_matches.push(_rt_root_node);
_rt_root_node?.querySelectorAll?.('[xu-ui-id]')?.forEach(function (el) { _rt_matches.push(el); });
return func.runtime.ui._wrap_matches(_rt_matches);
};
func.UI.worker.mark_pending_delete_session = function (SESSION_ID) {
func.UI.worker.ensure_runtime_indexes();
if (SESSION_ID) {
UI_WORKER_OBJ.pending_delete_sessions.add(SESSION_ID);
}
};
func.UI.worker.mark_pending_delete_element = function (SESSION_ID, target) {
func.UI.worker.ensure_runtime_indexes();
if (!SESSION_ID || !target) {
return false;
}
if (!UI_WORKER_OBJ.pending_delete_elements_by_session[SESSION_ID]) {
UI_WORKER_OBJ.pending_delete_elements_by_session[SESSION_ID] = new Set();
}
const elements = Array.isArray(target) ? target : (target?.length !== undefined ? Array.from(target) : [func.runtime.ui.get_first_node(target)].filter(Boolean));
elements.forEach(function (element) {
UI_WORKER_OBJ.pending_delete_elements_by_session[SESSION_ID].add(element);
});
UI_WORKER_OBJ.pending_delete_sessions.add(SESSION_ID);
return true;
};
func.UI.worker.cleanup_pending_to_delete = function (SESSION_ID = Object.keys(SESSION_OBJ)[0]) {
func.UI.worker.ensure_runtime_indexes();
if (!UI_WORKER_OBJ.pending_delete_sessions.has(SESSION_ID)) {
return;
}
const pending_elements = UI_WORKER_OBJ.pending_delete_elements_by_session[SESSION_ID];
if (pending_elements?.size) {
pending_elements.forEach(function (element) {
const element_data = func.runtime.ui.get_data(element);
if (element_data?.xuData?.pending_to_delete) {
element_data.xuData.pending_to_delete = false;
}
});
pending_elements.clear();
} else {
const runtime_elements = func.UI.worker.get_session_runtime_elements(SESSION_ID);
for (let index = 0; index < runtime_elements.length; index++) {
const val = runtime_elements[index];
const element_data = func.runtime.ui.get_data(val);
if (element_data?.xuData?.pending_to_delete) {
element_data.xuData.pending_to_delete = false;
}
}
}
UI_WORKER_OBJ.pending_delete_sessions.delete(SESSION_ID);
};
func.UI.worker.init = async function (SESSION_ID) {
func.UI.worker.ensure_runtime_indexes();
func.UI.utils.isInViewport = function (el) {
var node = func.runtime.ui.get_first_node(el);
if (!node) return false;
var rect = node.getBoundingClientRect();
return rect.bottom > 0 && rect.top < (window.innerHeight || document.documentElement.clientHeight);
};
if (typeof $ !== 'undefined' && $.fn) {
$.fn.isInViewport = function () {
return func.UI.utils.isInViewport(this[0]);
};
}
if (!UI_WORKER_OBJ.in_flight_job_elements) {
UI_WORKER_OBJ.in_flight_job_elements = [];
}
const contains_job_element = function ($parent, $child) {
const parent_node = $parent?.[0];
const child_node = $child?.[0];
if (!parent_node || !child_node) {
return false;
}
return parent_node.contains(child_node);
};
// Cancel in-flight child jobs when a parent element is being removed/hidden.
// This prevents orphaned jobs whose DOM elements no longer exist.
func.UI.worker.cancel_child_in_flight_jobs = function ($parent_element) {
if (!$parent_element?.length || !UI_WORKER_OBJ.in_flight_job_elements.length) return;
const parent_node = $parent_element[0];
if (!parent_node) return;
for (let i = UI_WORKER_OBJ.in_flight_job_elements.length - 1; i >= 0; i--) {
const child_el = UI_WORKER_OBJ.in_flight_job_elements[i];
const child_node = child_el?.[0];
if (child_node && parent_node.contains(child_node)) {
UI_WORKER_OBJ.in_flight_job_elements.splice(i, 1);
}
}
};
const overlaps_any_in_flight = function (job) {
if (!job?.elementP) {
return false;
}
for (let index = 0; index < UI_WORKER_OBJ.in_flight_job_elements.length; index++) {
const active_el = UI_WORKER_OBJ.in_flight_job_elements[index];
if (!active_el) continue;
// Block only when the new job is a CHILD of an in-flight element.
// If the new job is a PARENT of an in-flight element, allow it to run
// (e.g. xu-render hiding a container while a child panel's on_load is still running).
if (contains_job_element(active_el, job.elementP)) {
return true;
}
}
return false;
};
const job_iterator = async function (run_mode = 'data') {
const mark_pending_delete_on_descendants = function ($element) {
if (!$element?.length) {
return;
}
const descendants = $element[0]?.querySelectorAll?.('[xu-ui-id]') || [];
for (let index = 0; index < descendants.length; index++) {
const val = descendants[index];
const elm_data = func.runtime.ui.get_data(val);
if (!elm_data?.xuData) {
continue;
}
elm_data.xuData.pending_to_delete = true;
func.UI.worker.mark_pending_delete_element(SESSION_ID, val);
}
};
// XU_PERF: 8 dom jobs/frame turns a 10k-element refresh into ~1,250 paced
// frames of mostly idle; a 512 dispatch budget drains it in a few passes
const dom_job_budget = run_mode === 'dom' ? (glb.XU_PERF ? 512 : UI_WORKER_OBJ.dom_jobs_per_frame) : Number.POSITIVE_INFINITY;
let dom_jobs_processed = 0;
const dispatched_jobs = [];
if (UI_WORKER_OBJ.active_jobs_count) {
func.UI.worker.idle = 0;
// XU_PERF: skip the hole prefix instead of rescanning it every pass
const scan_start = glb.XU_PERF && UI_WORKER_OBJ.first_active_job_index ? UI_WORKER_OBJ.first_active_job_index : 0;
for (let key = scan_start; key < UI_WORKER_OBJ.jobs.length; key++) {
const val = UI_WORKER_OBJ.jobs[key];
try {
if (!val) continue;
if (val.stat === 'busy') continue;
const job_lane = val.lane || func.UI.worker.get_job_lane(val.functionP);
val.lane = job_lane;
if (run_mode === 'data' && job_lane !== 'data') continue;
if (job_lane === 'dom' && dom_jobs_processed >= dom_job_budget) continue;
// skip jobs whose element overlaps with any currently in-flight job (parent/child)
// sibling containers can run in parallel
if (overlaps_any_in_flight(val)) continue;
if (!val.elementP) {
const job_promise = func.UI.worker.execute(val.SESSION_ID, val).catch(function (err) {
console.error(err);
});
dispatched_jobs.push(job_promise);
if (job_lane === 'dom') {
dom_jobs_processed++;
}
continue;
}
const active_xu_ui_id = func.runtime.ui.get_attr(val.elementP, 'xu-ui-id');
if (!active_xu_ui_id) continue;
const running_job_obj = func.UI.worker.get_first_active_job();
if (!running_job_obj) {
break;
}
if (running_job_obj.job_num !== val.job_num) {
// skip - if job element exist in the active job ui as child element
if (contains_job_element(running_job_obj.elementP, val.elementP)) {
continue;
}
// keep overlapping parent/child jobs serialized; siblings can run in-flight together
if (contains_job_element(val.elementP, running_job_obj.elementP)) {
continue;
}
}
// Track this element as in-flight so future schedule_run passes skip overlapping jobs
const in_flight_entry = val.elementP;
UI_WORKER_OBJ.in_flight_job_elements.push(in_flight_entry);
// execute siblings/non-overlapping jobs — fire and forget, re-trigger scheduler on completion
const job_promise = func.UI.worker.execute(val.SESSION_ID, val).catch(function (err) {
console.error(err);
}).finally(function () {
// remove this element from in-flight tracking
const idx = UI_WORKER_OBJ.in_flight_job_elements.indexOf(in_flight_entry);
if (idx !== -1) {
UI_WORKER_OBJ.in_flight_job_elements.splice(idx, 1);
}
// re-trigger scheduler so blocked sibling jobs can now run
func.UI.worker.schedule_run(0);
});
dispatched_jobs.push(job_promise);
if (job_lane === 'dom') {
dom_jobs_processed++;
}
continue;
} catch (err) {
console.error(err);
}
}
// Only await jobs that have no element (data jobs) — don't block on long-running UI jobs
// UI jobs with elements will re-trigger schedule_run when they complete
}
};
func.UI.worker.schedule_run = function (delay = 0, preferred_mode) {
func.UI.worker.ensure_runtime_indexes();
if (UI_WORKER_OBJ.run_in_progress) {
// Allow re-entry: even though a scan is in progress, mark run_again
// so the scheduler re-scans after the current pass. This lets sibling
// jobs that were queued after the scan started get picked up quickly.
UI_WORKER_OBJ.run_again = true;
return;
}
const run_mode = preferred_mode || func.UI.worker.get_pending_run_mode() || 'data';
if (UI_WORKER_OBJ.run_timer) {
const should_promote_data =
run_mode === 'data' &&
UI_WORKER_OBJ.run_schedule_type &&
UI_WORKER_OBJ.run_schedule_type !== 'data';
if (!should_promote_data) {
return;
}
func.UI.worker.cancel_pending_run();
}
const run_once = async function () {
UI_WORKER_OBJ.run_timer = null;
UI_WORKER_OBJ.run_schedule_type = null;
UI_WORKER_OBJ.run_in_progress = true;
try {
await job_iterator(run_mode);
} finally {
UI_WORKER_OBJ.run_in_progress = false;
func.UI.worker.maybe_compact_jobs();
const next_run_mode = func.UI.worker.get_pending_run_mode();
const should_continue = UI_WORKER_OBJ.run_again || !!next_run_mode;
UI_WORKER_OBJ.run_again = false;
if (should_continue) {
func.UI.worker.schedule_run(0, next_run_mode || undefined);
}
}
};
UI_WORKER_OBJ.run_schedule_type = run_mode;
if (run_mode === 'dom') {
if (delay > 0) {
UI_WORKER_OBJ.run_schedule_type = 'dom_delay';
UI_WORKER_OBJ.run_timer = setTimeout(function () {
UI_WORKER_OBJ.run_schedule_type = 'dom_frame';
UI_WORKER_OBJ.run_timer = func.UI.worker.request_frame(run_once);
}, delay);
} else {
UI_WORKER_OBJ.run_schedule_type = 'dom_frame';
UI_WORKER_OBJ.run_timer = func.UI.worker.request_frame(run_once);
}
} else {
UI_WORKER_OBJ.run_schedule_type = 'data';
UI_WORKER_OBJ.run_timer = setTimeout(run_once, delay);
}
this._interval = UI_WORKER_OBJ.run_timer;
};
if (!UI_WORKER_OBJ.maintenance_intervals_initialized) {
UI_WORKER_OBJ.maintenance_intervals_initialized = true;
setInterval(async function () {
func.UI.ds_garbage_collector();
}, 10000);
setInterval(async function () {
func.UI.worker.cleanup_pending_to_delete();
func.UI.refs_garbage_collector();
func.UI.teleport_garbage_collector();
}, 1000);
}
};
func.UI.worker.add_to_queue = async function (SESSION_ID, source, functionP, paramsP, calling_job, elementP, dsSession, calling_trigger_prop) {
func.UI.worker.ensure_runtime_indexes();
var obj = {
SESSION_ID,
source,
functionP,
paramsP,
calling_job,
elementP,
dsSession,
calling_trigger_prop,
job_num: UI_WORKER_OBJ.num,
};
obj.lane = func.UI.worker.get_job_lane(functionP);
const get_ui_id = function (target) {
return func.runtime?.ui?.get_attr ? func.runtime.ui.get_attr(target, 'xu-ui-id') : target?.attr?.('xu-ui-id');
};
const get_parent_ui_id = function (target) {
const parent_node = func.runtime.ui.get_first_node(target)?.parentElement;
if (!parent_node) {
return '';
}
return get_ui_id(parent_node);
};
const get_queue_key = function () {
if (functionP === 'update_datasource') {
return source + '_' + functionP + '_' + (dsSession || '') + '_' + (paramsP?.currentRecordId || '') + '_' + (paramsP?.field_id || '');
}
if (functionP === 'render_viewport') {
return source + '_' + functionP + '_' + (get_ui_id(paramsP?.$div) || '');
}
if (functionP === 'set_viewport_height') {
return source + '_' + functionP + '_' + (get_parent_ui_id(paramsP?.$div) || '');
}
if (functionP === 'execute_xu_all_attributes') {
return source + '_' + functionP + '_' + (get_ui_id(elementP) || '') + '_' + (paramsP?.fields_arr?.toString() || '');
}
if (functionP === 'execute_xu_render_attributes') {
return source + '_' + functionP + '_' + (get_ui_id(elementP) || '') + '_' + (paramsP?.attr_value?.toString?.() || paramsP?.attr_value || '') + '_' + (paramsP?.fields_arr?.toString() || '');
}
if (functionP === 'execute_xu_for') {
return source + '_' + functionP + '_' + (get_ui_id(elementP) || '') + '_' + (paramsP?.xu_for_item_id || '');
}
if (functionP === 'execute_xu_widget') {
return source + '_' + functionP + '_' + (get_ui_id(elementP) || '');
}
return null;
};
const queue_key = get_queue_key();
if (queue_key) {
obj.queue_key = queue_key;
let exist_job = UI_WORKER_OBJ.jobs_by_queue_key?.[queue_key];
if (exist_job) {
func.runtime?.perf?.increment?.(SESSION_ID, 'jobs_coalesced');
func.runtime?.perf?.increment_map?.(SESSION_ID, 'jobs_coalesced_by_function', functionP);
exist_job.paramsP = paramsP;
exist_job.calling_trigger_prop = calling_trigger_prop;
exist_job.elementP = elementP;
exist_job.dsSession = dsSession;
exist_job.lane = obj.lane;
return exist_job.job_num;
}
}
// if (calling_job) {
// var job_index = func.UI.worker.find_job_index(SESSION_ID, calling_job);
// if (job_index === null || typeof job_index === 'undefined') return;
// try {
// if (!UI_WORKER_OBJ.jobs[job_index].splice_count) {
// UI_WORKER_OBJ.jobs[job_index].splice_count = 0;
// }
// UI_WORKER_OBJ.jobs[job_index].splice_count++;
// UI_WORKER_OBJ.jobs.splice(job_index + UI_WORKER_OBJ.jobs[job_index].splice_count, 0, obj);
// // }
// } catch (e) {
// console.error('bug');
// // UI_WORKER_OBJ.jobs.splice(0, 0, obj);
// }
// } else {
// // check case of xu-render if queue has child node of the element then delete job
// // check case of execute attributes that queue not contain the element then add to parallel queue
// UI_WORKER_OBJ.jobs.push(obj);
// }
UI_WORKER_OBJ.jobs.push(obj);
func.UI.worker.register_job(obj, UI_WORKER_OBJ.jobs.length - 1);
func.runtime?.perf?.increment?.(SESSION_ID, 'jobs_queued');
func.runtime?.perf?.increment_map?.(SESSION_ID, 'jobs_queued_by_function', functionP);
if (func.UI.worker.schedule_run) {
func.UI.worker.schedule_run(0, obj.lane === 'data' ? 'data' : undefined);
}
UI_WORKER_OBJ.num++;
return UI_WORKER_OBJ.num - 1;
};
func.UI.worker.delete_job = async function (SESSION_ID, jobNoP) {
// func.UI.worker.ID.postMessage({
// method: "delete_job",
// params: {
// job_num: jobNoP,
// },
// });
// return;
var _session = SESSION_OBJ[SESSION_ID];
var job_index = func.UI.worker.find_job_index(SESSION_ID, jobNoP);
if (job_index === null || typeof job_index === 'undefined' || !UI_WORKER_OBJ.jobs[job_index]) {
// UI_WORKER_OBJ.stat = null;
return;
}
const removed_job = UI_WORKER_OBJ.jobs[job_index];
func.runtime?.perf?.increment?.(SESSION_ID, 'jobs_completed');
func.runtime?.perf?.increment_map?.(SESSION_ID, 'jobs_completed_by_function', removed_job.functionP);
var dsSession = removed_job.dsSession;
let ds_obj = _session?.DS_GLB[dsSession];
if (ds_obj) {
delete SCREEN_BLOCKER_OBJ[ds_obj.screenId + (ds_obj.callingScreenId ? '_' + ds_obj.callingScreenId : '')];
}
if (dsSession && ds_obj?.loops_limit && ds_obj?.loops_count < ds_obj?.loops_limit - 1) {
return;
}
// UI_WORKER_OBJ.stat = null;
func.UI.worker.unregister_job(removed_job);
UI_WORKER_OBJ.jobs[job_index] = null;
UI_WORKER_OBJ.job_holes_count++;
if (UI_WORKER_OBJ.first_active_job_index === job_index) {
func.UI.worker.advance_first_active_job_index(job_index + 1);
}
func.UI.worker.maybe_compact_jobs(job_index === 0);
};
func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
var job_index = func.UI.worker.find_job_index(SESSION_ID, queue_obj.job_num);
// if (UI_WORKER_OBJ.jobs?.[job_index]?.stat === 'busy') {
// if (queue_obj.jobNoP) UI_WORKER_OBJ.stat = job_index;
// return;
// }
// if (queue_obj.jobNoP && !UI_WORKER_OBJ.jobs[job_index]) {
// UI_WORKER_OBJ.stat = null;
// return;
// }
if (queue_obj.jobNoP) UI_WORKER_OBJ.stat = job_index;
if (UI_WORKER_OBJ.jobs[job_index]) {
UI_WORKER_OBJ.jobs[job_index].stat = 'busy';
}
func.runtime?.perf?.increment?.(SESSION_ID, 'jobs_executed');
func.runtime?.perf?.increment_map?.(SESSION_ID, 'jobs_executed_by_function', queue_obj.functionP);
const fx = {
get_element_by_ui_id: function (xu_ui_id) {
if (!xu_ui_id) {
return func.runtime.ui._wrap_matches([]);
}
if (func.runtime?.ui?.get_refresh_indexed_element_by_ui_id) {
const _elm = func.runtime.ui.get_refresh_indexed_element_by_ui_id(SESSION_ID, xu_ui_id);
return func.runtime.ui._wrap_matches(_elm ? [_elm] : []);
}
const $root = func.UI.worker.get_session_root(SESSION_ID);
if (func.runtime?.ui?.find_refresh_elements_by_attr) {
return func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-ui-id', xu_ui_id, true);
}
return func.runtime.ui.find_by_selector($root, `[xu-ui-id="${xu_ui_id}"]`);
},
get_live_element_context: function (elem_key, fallback_$elm, context = {}) {
let raw_$elm = elem_key ? fx.get_element_by_ui_id(elem_key) : fallback_$elm;
let $elm = func.runtime?.ui?.get_preferred_live_element ? func.runtime.ui.get_preferred_live_element(raw_$elm) : raw_$elm;
const matches_context = function (node_xu_data, allow_loose_match = false) {
if (!node_xu_data) {
return false;
}
if (context.node_id && node_xu_data.nodeid !== context.node_id) {
return false;
}
if (typeof context.key !== 'undefined' && context.key !== null && node_xu_data.key !== context.key) {
return false;
}
if (context.key_path && node_xu_data.key_path !== context.key_path) {
return false;
}
if (context.recordid && node_xu_data.recordid !== context.recordid) {
return false;
}
if (!allow_loose_match) {
if (context.prog_id && node_xu_data.paramsP?.prog_id !== context.prog_id) {
return false;
}
if (context.parent_element_ui_id && node_xu_data.parent_element_ui_id !== context.parent_element_ui_id) {
return false;
}
}
if (node_xu_data.pending_to_delete) {
return false;
}
return true;
};
const resolved_node = func.runtime.ui.get_first_node($elm);
const resolved_data = resolved_node ? func.runtime.ui.get_data(resolved_node)?.xuData : null;
const has_matching_resolved_node = matches_context(resolved_data);
if (context.node_id && !has_matching_resolved_node) {
const $root = func.UI.worker.get_session_root(SESSION_ID);
const runtime_nodes = func.runtime?.ui?.get_refresh_index_elements
? func.runtime.ui.get_refresh_index_elements(SESSION_ID, $root).toArray()
: Array.from(func.runtime.ui.get_first_node($root)?.querySelectorAll('[xu-ui-id]') || []);
let matching_nodes = [];
for (let index = 0; index < runtime_nodes.length; index++) {
const node = runtime_nodes[index];
const node_data = func.runtime.ui.get_data(node);
const node_xu_data = node_data?.xuData;
if (!matches_context(node_xu_data)) {
continue;
}
matching_nodes.push(node);
}
if (!matching_nodes.length) {
for (let index = 0; index < runtime_nodes.length; index++) {
const node = runtime_nodes[index];
const node_data = func.runtime.ui.get_data(node);
const node_xu_data = node_data?.xuData;
if (!matches_context(node_xu_data, true)) {
continue;
}
matching_nodes.push(node);
}
}
if (matching_nodes.length) {
raw_$elm = func.runtime.ui._wrap_matches(matching_nodes);
$elm = func.runtime?.ui?.get_preferred_live_element ? func.runtime.ui.get_preferred_live_element(raw_$elm) : raw_$elm;
}
}
const _resolved_node = func.runtime.ui.get_first_node($elm);
if (!_resolved_node) {
return {
$elm: func.runtime.ui._wrap_matches([]),
data: null,
};
}
if (!$elm?.length) {
$elm = func.runtime.ui._wrap_matches([_resolved_node]);
}
const data = func.runtime.ui.get_data($elm);
return {
$elm,
data,
};
},
get_child_node_by_id: function (xuData, node_id) {
if (!xuData?.node_org?.children?.length || !node_id) {
return null;
}
if (func.runtime?.ui?.get_node_children_by_id) {
const children_by_id = func.runtime.ui.get_node_children_by_id(xuData.node_org);
return children_by_id[node_id] || null;
}
if (!xuData.node_org_children_by_id) {
xuData.node_org_children_by_id = {};
for (let index = 0; index < xuData.node_org.children.length; index++) {
const child_node = xuData.node_org.children[index];
if (child_node?.id) {
xuData.node_org_children_by_id[child_node.id] = child_node;
}
}
}
return xuData.node_org_children_by_id[node_id] || null;
},
get_refresh_execution_plan: function (live_data, refresh_attributes) {
const xuData = live_data?.xuData;
if (!xuData) {
return [];
}
if (!xuData.refresh_execution_plan_cache) {
xuData.refresh_execution_plan_cache = {};
}
const cache_key = (refresh_attributes || []).join('|');
if (xuData.refresh_execution_plan_cache[cache_key]) {
return xuData.refresh_execution_plan_cache[cache_key];
}
const skip_attributes = {
'xu-exp:xu-render': true,
'xu-exp:xu-for': true,
'xu-for': true,
'xu-exp:xu-bind': true,
};
const raw_value_attributes = {
'xu-bind': true,
'xu-ref': true,
'xu-on': true,
'xu-for-key': true,
'xu-for-val': true,
'xu-click': true,
'xu-change': true,
'xu-blur': true,
'xu-focus': true,
'xu-init': true,
'xu-attrs': true,
'xu-cdn': true,
'xu-style': true,
'xu-style-global': true,
'xu-script': true,
'xu-viewport': true,
'xu-ui-plugin': true,
};
const execution_plan = [];
for (let index = 0; index < (refresh_attributes || []).length; index++) {
const attr = refresh_attributes[index];
if (!attr || skip_attributes[attr]) {
continue;
}
const attr_new = attr.split('xu-exp:')[1];
const xu_func = attr_new || attr;
const is_raw_value_attribute = !!raw_value_attributes[attr] || attr.substr(0, 6) === 'xu-on:';
execution_plan.push({
attr,
attr_new,
xu_func,
is_regular_attribute: !!(attr_new && attr_new.substr(0, 2) !== 'xu'),
requires_expression: !is_raw_value_attribute && attr !== 'xu-class' && attr !== 'xu-ui-plugin',
regular_attr_name: attr_new ? (attr_new !== 'viewBox' ? attr_new.toLowerCase() : attr_new) : null,
});
}
xuData.refresh_execution_plan_cache[cache_key] = execution_plan;
return execution_plan;
},
get_expression_cache_key: function (options) {
const iterate_info = options.iterate_info;
const iterate_key = iterate_info
? [
iterate_info.iterator_key ?? '',
iterate_info.iterator_val ?? '',
iterate_info._key ?? '',
iterate_info._val ?? '',
].join('::')
: '';
return [
options.expression_text,
options.dsSessionP,
options.recordid,
iterate_key,
].join('||');
},
get_expression_result: async function (cache, options) {
const cache_key = fx.get_expression_cache_key(options);
if (Object.prototype.hasOwnProperty.call(cache, cache_key)) {
return cache[cache_key];
}
const result = await func.expression.get(
options.SESSION_ID,
options.expression_text,
options.dsSessionP,
'UI Property EXP',
options.recordid,
null,
null,
null,
null,
null,
options.iterate_info,
);
cache[cache_key] = result;
return result;
},
update_datasource: async function (currentRecordId) {
if (queue_obj?.paramsP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[queue_obj.dsSession];
_ds.currentRecordId = currentRecordId || queue_obj.paramsP.currentRecordId;
// console.info(queue_obj.dsSession, _ds.currentRecordId);
var datasource_changes = {
[_ds.dsSession]: { [_ds.currentRecordId]: 'set' },
};
await func.datasource.update(SESSION_ID, datasource_changes);
if (queue_obj.paramsP.field_id) {
datasource_changes = {
[_ds.dsSession]: {
[_ds.currentRecordId]: {
[queue_obj.paramsP.field_id]: queue_obj.paramsP.field_value,
},
},
};
await func.datasource.update(SESSION_ID, datasource_changes);
}
}
if (!currentRecordId) return await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
},
execute_xu_render_attributes: async function () {
const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'execute_xu_render_attributes');
const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj.paramsP?.elem_val?.$elm, queue_obj.paramsP);
const _data = live_context.data;
try {
if (_data?.xuData?.paramsP) {
const live_xu_data = _data.xuData;
await func.runtime.render.execute_xu_function({
SESSION_ID,
is_skeleton: null,
$root_container: live_xu_data.$root_container,
nodeP: live_xu_data.node,
$container: fx.get_element_by_ui_id(live_xu_data.parent_element_ui_id) || live_xu_data.$container,
paramsP: live_xu_data.paramsP,
parent_infoP: live_xu_data.iterate_info ? { iterate_info: live_xu_data.iterate_info } : {},
jobNoP: queue_obj.jobNoP,
keyP: live_xu_data.key,
parent_nodeP: live_xu_data.parent_node,
xu_func: 'xu-render',
$elm: live_context.$elm,
$live_elm: live_context.$elm,
val: {
key: 'xu-render',
value: queue_obj.paramsP.attr_value,
fields_arr: queue_obj.paramsP.fields_arr,
jobNoP: queue_obj.jobNoP,
},
get_params_obj_new: func.runtime.program.get_params_obj,
});
}
return await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
} finally {
perf_end?.();
}
},
execute_xu_all_attributes: async function () {
const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'execute_xu_all_attributes');
const done = function () {
return func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
};
try {
const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj.paramsP?.elem_val?.$elm, queue_obj.paramsP);
const $elm = live_context.$elm;
const live_data = live_context.data;
const elm_node = $elm?.[0];
if (!$elm.length || !live_data?.xuData || !live_data?.xuAttributes) {
return done();
}
const live_xu_data = live_data.xuData;
const live_xu_attributes = live_data.xuAttributes;
const refresh_attributes = queue_obj.paramsP?.elem_val?.attributes || [];
const execution_plan = fx.get_refresh_execution_plan(live_data, refresh_attributes);
const expression_results_cache = {};
const live_parent_container = fx.get_element_by_ui_id(live_xu_data.parent_element_ui_id);
const xu_execution_context = {
SESSION_ID,
is_skeleton: null,
$root_container: live_xu_data.$root_container,
nodeP: live_xu_data.node,
$container: live_parent_container?.length ? live_parent_container : live_xu_data.$container,
paramsP: live_xu_data.paramsP,
parent_infoP: live_xu_data.iterate_info ? { iterate_info: live_xu_data.iterate_info } : {},
jobNoP: queue_obj.jobNoP,
keyP: live_xu_data.key,
parent_nodeP: live_xu_data.parent_node,
$elm,
$live_elm: $elm,
get_params_obj_new: func.runtime.program.get_params_obj,
};
const handler_bundle = func.runtime.render.build_xu_handlers(xu_execution_context, SESSION_OBJ[SESSION_ID].DS_GLB[live_xu_data.paramsP.dsSessionP]);
for (let index = 0; index < execution_plan.length; index++) {
const plan = execution_plan[index];
const attr = plan.attr;
const attr_value = live_xu_attributes[attr];
if (typeof attr_value === 'undefined') {
continue;
}
let result = attr_value;
if (plan.requires_expression) {
const expression_result = await fx.get_expression_result(expression_results_cache, {
SESSION_ID: queue_obj.paramsP.SESSION_ID,
expression_text: attr_value,
dsSessionP: live_xu_data.paramsP.dsSessionP,
recordid: live_xu_data.recordid,
iterate_info: live_xu_data.iterate_info,
});
result = expression_result.result;
}
if (plan.is_regular_attribute) {
if (plan.regular_attr_name === 'class') {
func.runtime.render.apply_expression_class($elm, result);
} else {
func.runtime.ui.set_attr($elm, plan.regular_attr_name, result);
}
} else {
try {
await func.runtime.render.execute_xu_function({
...xu_execution_context,
xu_func: plan.xu_func,
val: {
key: plan.xu_func,
value: result,
},
handler_bundle,
});
} catch (error) {
console.error('[xuda-runtime] caught xuda_UI.utils.js:1909:', error);
}
}
}
return done();
} finally {
perf_end?.();
}
},
execute_xu_for: async function () {
const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'execute_xu_for');
try {
const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj?.paramsP?.elem_val?.$elm, queue_obj.paramsP);
var $elm = live_context.$elm; // $(SESSION_OBJ[SESSION_ID].root_element).find(`[xu-ui-id=${queue_obj.paramsP.elem_key}]`)
if (!$elm?.length) {
return await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
}
const existing_children_map = new Set();
if (func.runtime?.ui?.get_refresh_indexed_elements_by_node_id) {
const indexed_children = func.runtime.ui.get_refresh_indexed_elements_by_node_id(SESSION_ID, queue_obj?.paramsP?.xu_for_item_id).toArray();
for (let index = 0; index < indexed_children.length; index++) {
if (indexed_children[index].parentElement === func.runtime.ui.get_first_node($elm)) {
existing_children_map.add(indexed_children[index]);
}
}
}
const children = func.runtime.ui.get_children($elm);
for (let index = 0; index < children.length; index++) {
const child = children[index];
const child_data = func.runtime.ui.get_data(child);
if (child_data?.xuData?.nodeid === queue_obj?.paramsP?.xu_for_item_id) {
existing_children_map.add(child);
}
}
const _reuse_key = `${queue_obj.paramsP.elem_key}::${queue_obj?.paramsP?.xu_for_item_id}`;
if (existing_children_map.size) {
if (glb.XU_PERF) {
// hand the live rows to handle_xu_for for keyed reuse instead of
// tearing them down; whatever it does not consume is removed below
func.runtime.render.xu_for_reuse_stash = func.runtime.render.xu_for_reuse_stash || new Map();
func.runtime.render.xu_for_reuse_stash.set(_reuse_key, Array.from(existing_children_map));
} else {
func.runtime.ui.remove(func.runtime.ui._wrap_matches(Array.from(existing_children_map)));
}
}
let _data = live_context.data || func.runtime.ui.get_data($elm);
const node_to_render = fx.get_child_node_by_id(_data?.xuData, queue_obj?.paramsP?.xu_for_item_id);
await func.runtime.render.render_ui_tree(queue_obj.paramsP.SESSION_ID, $elm, node_to_render, null, _data.xuData.paramsP, queue_obj.jobNoP, null, _data.xuData.key, null, _data.xuData.parent_node, null, _data.xuData.$root_container);
if (glb.XU_PERF) {
// stash not consumed (id mismatch, non-array source, error): finish
// the legacy teardown so old rows can never linger next to new ones
const _leftover = func.runtime.render.xu_for_reuse_stash.get(_reuse_key);
if (_leftover) {
func.runtime.render.xu_for_reuse_stash.delete(_reuse_key);
const _still = _leftover.filter((el) => el && el.isConnected);
if (_still.length) func.runtime.ui.remove(func.runtime.ui._wrap_matches(_still));
}
}
} catch (error) {
} finally {
perf_end?.();
}
return await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
},
render_viewport: async function () {
const { nodeP, $div, parent_infoP, $root_container, jobNoP, is_skeleton, paramsP, $container } = queue_obj?.paramsP || {};
const _div_node = func.runtime.ui.get_first_node($div);
if (nodeP?.children?.length && !_div_node?.children?.length) {
await fx.update_datasource(func.runtime.ui.get_data($div)?.xuData?.currentRecordId);
func.runtime.ui.remove_class($div, 'skeleton');
for (let index = 0; index < nodeP.children.length; index++) {
await func.runtime.render.render_ui_tree(SESSION_ID, $div, nodeP.children[index], parent_infoP, paramsP, jobNoP, is_skeleton, index, null, nodeP, null, $root_container);
}
_div_node.style.removeProperty('height');
func.runtime.ui.get_data($div).xuData.viewport_height = _div_node?.offsetHeight || 0;
// set initial default height for all children
const parent_id = func.runtime.ui.get_attr(_div_node?.parentElement, 'xu-ui-id');
func.UI.worker.ensure_runtime_indexes();
if (!UI_WORKER_OBJ.viewport_height_set_ids_set.has(parent_id)) {
UI_WORKER_OBJ.viewport_height_set_ids_set.add(parent_id);
UI_WORKER_OBJ.viewport_height_set_ids.push(parent_id);
func.UI.worker.add_to_queue(SESSION_ID, 'gui event', 'set_viewport_height', { $div, height: func.runtime.ui.get_first_node($div)?.offsetHeight || 0 }, null, null, paramsP.dsSessionP);
}
}
return await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
},
set_viewport_height: async function () {
const { $div, height } = queue_obj?.paramsP || {};
const siblings = func.runtime.ui.get_first_node($div)?.parentElement?.children || [];
for (let index = 0; index < siblings.length; index++) {
const elm = siblings[index];
if (!elm.style.height && !elm.childElementCount) {
func.runtime.ui.set_style(elm, 'height', `${height}px`);
}
}
return await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
},
execute_xu_widget: async function () {
try {
const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj?.paramsP?.elem_val?.$elm, queue_obj.paramsP);
var $elm = live_context.$elm; // $(SESSION_OBJ[SESSION_ID].root_element).find(`[xu-ui-id=${queue_obj.paramsP.elem_key}]`)
if (!$elm?.length) {
return await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
}
// $elm.empty();
// $.each($elm.children(), (key, val) => {
// if (!$(val)?.data()?.xuData) return true;
// if ($(val).data().xuData.nodeid === queue_obj?.paramsP?.xu_for_item_id) {
// $(val).remove();
// }
// });
const parent_id = func.runtime.ui.get_attr(func.runtime.ui.get_first_node($elm)?.parentElement, 'xu-ui-id');
let _data = live_context.data || func.runtime.ui.get_data($elm);
func.runtime.ui.remove($elm);
if (!_data?.xuData) {
return await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
}
const node_to_render = _data.xuData.node_org; //_data.xuData.node_org.children?.find((e) => e.id === queue_obj?.paramsP?.xu_for_item_id);
await func.runtime.render.render_ui_tree(queue_obj.paramsP.SESSION_ID, fx.get_element_by_ui_id(parent_id), node_to_render, null, _data.xuData.paramsP, queue_obj.jobNoP, null, _data.xuData.key, null, _data.xuData.parent_node, null, _data.xuData.$root_container);
} catch (error) {}
return await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
},
};
try {
return await fx[queue_obj.functionP]();
} catch (error) {
console.error(error);
const failed_job_index = func.UI.worker.find_job_index(SESSION_ID, queue_obj.job_num);
if (failed_job_index !== null && typeof failed_job_index !== 'undefined' && UI_WORKER_OBJ.jobs[failed_job_index]) {
await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
}
return null;
}
};
func.UI.worker.find_job_index = function (SESSION_ID, jobNoP) {
var ret = null;
if (!UI_WORKER_OBJ) return ret;
func.UI.worker.ensure_runtime_indexes();
if (Object.prototype.hasOwnProperty.call(UI_WORKER_OBJ.job_index_by_num, jobNoP)) {
const indexed_job = UI_WORKER_OBJ.job_index_by_num[jobNoP];
if (UI_WORKER_OBJ.jobs[indexed_job]) {
return indexed_job;
}
delete UI_WORKER_OBJ.job_index_by_num[jobNoP];
}
for (let key = 0; key < UI_WORKER_OBJ.jobs.length; key++) {
const val = UI_WORKER_OBJ.jobs[key];
if (val && val.job_num == jobNoP) {
ret = key;
UI_WORKER_OBJ.job_index_by_num[jobNoP] = ret;
break;
}
}
return ret;
};
func.UI.ds_garbage_collector = function (SESSION_ID = Object.keys(SESSION_OBJ)[0], re_check) {
let _session = SESSION_OBJ[SESSION_ID];
const _data_system = _session?.DS_GLB?.[0]?.data_system;
if (_data_system?.SYS_GLOBAL_BOL_AJAX_BUSY) return;
if (!_data_system?.SYS_GLOBAL_BOL_IDLE) return;
const ds_keys = Object.keys(_session.DS_GLB || {});
let abort = false;
for (let index = 0; index < ds_keys.length; index++) {
const _ds = _session.DS_GLB[ds_keys[index]];
if (_ds.stat === 'busy') {
abort = true;
break;
}
}
if (abort) {
return;
}
const ds_pending_to_delete = new Set();
const active_ds_sessions = new Set();
const runtime_elements = func.UI.worker.get_session_runtime_elements(SESSION_ID);
for (let index = 0; index < runtime_elements.length; index++) {
const val = runtime_elements[index];
const dsSessionP = func.runtime.ui.get_data(val)?.xuData?.paramsP?.dsSessionP;
if (typeof dsSessionP === 'undefined' || dsSessionP === null) continue;
active_ds_sessions.add(dsSessionP.toString());
}
for (let index = 0; index < ds_keys.length; index++) {
const dsP = ds_keys[index];
const _ds = _session.DS_GLB[dsP];
if (!_ds.screen_params) continue;
if (!active_ds_sessions.has(dsP.toString())) {
ds_pending_to_delete.add(dsP.toString());
}
}
// console.log(ds_pending_to_delete);
const sortedKeys = ds_keys.slice().sort((a, b) => b.localeCompare(a));
for (let index = 0; index < sortedKeys.length; index++) {
const key = sortedKeys[index];
const val = _session.DS_GLB[key];
const parent_ds = typeof val.parentDataSourceNo !== 'undefined' ? val.parentDataSourceNo.toString() : null;
if (!ds_pending_to_delete.has(key) && parent_ds && ds_pending_to_delete.has(parent_ds)) {
ds_pending_to_delete.delete(parent_ds);
}
}
// console.log(ds_pending_to_delete);
for (const val of ds_pending_to_delete) {
func.datasource.del(SESSION_ID, val);
}
};
func.UI.refs_garbage_collector = function (SESSION_ID = Object.keys(SESSION_OBJ)[0]) {
if (!xu_isEmpty(SCREEN_BLOCKER_OBJ)) {
// let dom to finish build
return;
}
let _session = SESSION_OBJ[SESSION_ID];
const _data_system = _session?.DS_GLB?.[0]?.data_system;
const refs_obj = _data_system?.SYS_GLOBAL_OBJ_REFS || {};
const ref_keys = Object.keys(refs_obj);
for (let ref_index = 0; ref_index < ref_keys.length; ref_index++) {
const key = ref_keys[ref_index];
const val = refs_obj[key];
const $root = func.UI.worker.get_session_root(SESSION_ID);
const _raw_el = func.runtime?.ui?.get_refresh_indexed_element_by_ui_id
? func.runtime.ui.get_refresh_indexed_element_by_ui_id(SESSION_ID, val.xu_ui_id)
: null;
const $element = _raw_el
? func.runtime.ui._wrap_matches([_raw_el])
: func.runtime?.ui?.find_refresh_elements_by_attr
? func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-ui-id', val.xu_ui_id, true)
: func.runtime.ui.find_by_selector($root, `[xu-ui-id='${val.xu_ui_id}']`);
const $panel_wrapper = func.runtime?.ui?.find_panel_wrapper_in_root
? func.runtime.ui.find_panel_wrapper_in_root(SESSION_ID, val.xu_ui_id)
: func.runtime?.ui?.find_refresh_elements_by_attr
? func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-panel-wrapper-id', val.xu_ui_id, true)
: func.runtime.ui.find_by_selector($root, `[xu-panel-wrapper-id='${val.xu_ui_id}']`);
if (!$element?.length && !$panel_wrapper?.length) {
delete _data_system.SYS_GLOBAL_OBJ_REFS[key];
}
}
};
// Reconcile teleported content with its host. A xu-teleport moves content OUT
// of the host's subtree to a target, so the content's lifecycle is no longer
// tied to the host by the DOM. This keeps them in sync:
// - host removed -> drop the orphaned teleport
// - host hidden -> hide the teleport (it lives at the target, outside the
// host, and would otherwise stay visible while the host is
// hidden), and restore it when the host is shown again.
// Driven by the 1s maintenance tick (backstop) AND immediately whenever a
// screen settles (see the screen-blocker unblock hook in this file), so there
// is no visible lag on panel open/close.
func.UI.reconcile_teleports = function (SESSION_ID = Object.keys(SESSION_OBJ)[0]) {
if (func.UI._reconciling_teleports) {
return;
}
if (!xu_isEmpty(SCREEN_BLOCKER_OBJ)) {
// let dom finish building
return;
}
const $root = func.UI.worker.get_session_root(SESSION_ID);
// Match ALL teleported elements. Every xu-teleport-parent-id holds a host's
// xu-ui-id, and xu-ui-id is a 10-char base36 hash from func.common.fastHash
// (e.g. "a3x9k2m1p0") — it NEVER starts with "node-". The old `^='node-'`
// filter therefore matched zero elements, so this whole function was a silent
// no-op and orphaned teleports (e.g. the ילקוט דיגיטלי nav) were never
// collected when their host/panel closed. See handles in
// xuda_runtime.browser.handlers.nodes.js (handle_xu_teleport).
const teleport_elements = $root[0]?.querySelectorAll?.(`[xu-teleport-parent-id]`) || [];
if (!teleport_elements.length) {
return;
}
func.UI._reconciling_teleports = true;
try {
for (let index = 0; index < teleport_elements.length; index++) {
const val = teleport_elements[index];
const xu_teleport_parent_id = val.getAttribute('xu-teleport-parent-id');
const _raw_parent = func.runtime?.ui?.get_refresh_indexed_element_by_ui_id
? func.runtime.ui.get_refresh_indexed_element_by_ui_id(SESSION_ID, xu_teleport_parent_id)
: null;
const $parent = _raw_parent
? func.runtime.ui._wrap_matches([_raw_parent])
: func.runtime?.ui?.find_refresh_elements_by_attr
? func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-ui-id', xu_teleport_parent_id, true)
: func.runtime.ui.find_by_selector($root, `[xu-ui-id='${xu_teleport_parent_id}']`);
// Resolve a host node that is actually connected. The refresh-index lookup
// can hand back a DETACHED element (remove_xu_ui only marks the index
// dirty, and get_preferred_live_element returns a disconnected node when it
// is the only candidate), which used to make a removed panel look
// "present" and leak its teleport forever.
const parent_nodes = func.runtime.ui._to_node_array ? func.runtime.ui._to_node_array($parent) : [];
const host = parent_nodes.find((n) => n && n.isConnected);
if (!host) {
// Host removed -> the teleported content is orphaned; drop it.
func.runtime.ui.remove(val);
continue;
}
// Host present -> mirror its effective visibility onto the teleport, but
// ONLY when the host is a normal container (the "path A" host — a rendered
// parent DIV). In "path B" the host IS the <xu-teleport> source element,
// which handle_xu_teleport hides at its origin ON PURPOSE while the real
// content lives (visible) at the target; mirroring that intentional hidden
// state would wrongly hide legitimately-teleported content. The orphan
// removal above still applies to path B (source removed -> content drops).
const host_is_teleport_source = typeof host.tagName === 'string' && host.tagName.toLowerCase() === 'xu-teleport';
if (!host_is_teleport_source) {
// getClientRects().length === 0 catches display:none on the host or any
// ancestor; the hidden attribute is checked explicitly. Idempotent via
// the xu-teleport-hidden marker; original inline display saved/restored.
const host_hidden = host.hidden === true || (typeof host.getClientRects === 'function' && host.getClientRects().length === 0);
if (host_hidden) {
if (val.getAttribute('xu-teleport-hidden') !== '1') {
val.setAttribute('xu-teleport-hidden', '1');
val.setAttribute('xu-teleport-prev-display', val.style.display || '');
val.style.display = 'none';
}
} else if (val.getAttribute('xu-teleport-hidden') === '1') {
val.style.display = val.getAttribute('xu-teleport-prev-display') || '';
val.removeAttribute('xu-teleport-hidden');
val.removeAttribute('xu-teleport-prev-display');
}
}
}
} finally {
func.UI._reconciling_teleports = false;
}
};
// Back-compat alias: the 1s maintenance loop and any existing callers keep
// calling teleport_garbage_collector; it now also mirrors host visibility.
func.UI.teleport_garbage_collector = func.UI.reconcile_teleports;
func.UI.utils.prog_ui_attribute_index_cache = func.UI.utils.prog_ui_attribute_index_cache || new WeakMap();
func.UI.utils.get_prog_ui_attribute_index = function (progUi, prop, tag_name) {
if (!Array.isArray(progUi) || !prop) {
return {};
}
let prog_cache = func.UI.utils.prog_ui_attribute_index_cache.get(progUi);
if (!prog_cache) {
prog_cache = {};
func.UI.utils.prog_ui_attribute_index_cache.set(progUi, prog_cache);
}
const cache_key = `${prop || ''}::${tag_name || ''}`;
if (prog_cache[cache_key]) {
return prog_cache[cache_key];
}
const index = {};
const add_field = function (field_ref, item) {
if (!field_ref) {
return;
}
if (!index[field_ref]) {
index[field_ref] = [];
}
index[field_ref].push(item);
};
const iterate_progUi = function (node) {
for (let item of node) {
if ((!tag_name || item.tagName === tag_name) && !xu_isEmpty(item.attributes)) {
const attribute_keys = Object.keys(item.attributes);
for (let attr_index = 0; attr_index < attribute_keys.length; attr_index++) {
const attr = attribute_keys[attr_index];
const val = item.attributes[attr];
if (attr !== `xu-exp:${prop}` && attr !== prop) {
continue;
}
const attr_str = typeof val === 'string' ? val : JSON.stringify(val);
if (!attr_str) {
continue;
}
for (const match of attr_str.matchAll(/@([A-Za-z0-9_$.-]+)/g)) {
add_field(match[1], item);
}
}
}
if (item.children) {
iterate_progUi(item.children);
}
}
};
iterate_progUi(progUi);
prog_cache[cache_key] = index;
return index;
};
func.UI.find_field_in_progUi_attributes = function (progUi, field_id, prop, tag_name) {
if (prop) {
const index = func.UI.utils.get_prog_ui_attribute_index(progUi, prop, tag_name);
return index[field_id] || [];
}
let elm_nodes = [];
const iterate_progUi = function (node) {
for (let item of node) {
if (!tag_name || item.tagName === tag_name) {
if (tag_name) {
elm_nodes.push(item);
}
}
if (item.children) {
iterate_progUi(item.children);
}
}
};
iterate_progUi(progUi);
return elm_nodes;
};
func.UI.update_xu_ref = function (SESSION_ID, dsSessionP, ref_field_id, $elm) {
let ret;
const _session = SESSION_OBJ[SESSION_ID];
let _ds_0 = _session.DS_GLB[0];
const _ds = func.utils.clean_returned_datasource(SESSION_ID, dsSessionP);
// function createWatchedObject(obj, onChange) {
// const watchers = new WeakMap();
// function createProxy(target, path = []) {
// if (watchers.has(target)) {
// return watchers.get(target);
// }
// const proxy = new Proxy(target, {
// set(obj, prop, value) {
// const oldValue = obj[prop];
// let currentPath = [...path, prop];
// // Set the new value
// obj[prop] = value;
// // If the new value is an object, make it observable too
// if (typeof value === 'object' && value !== null) {
// obj[prop] = createProxy(value, currentPath);
// }
// // Notify of change
// if (oldValue !== value) {
// currentPath.shift();
// onChange({
// path: currentPath.join('.'),
// oldValue,
// newValue: value,
// type: 'set',
// timestamp: Date.now(),
// });
// }
// return true;
// },
// deleteProperty(obj, prop) {
// const oldValue = obj[prop];
// const currentPath = [...path, prop];
// delete obj[prop];
// onChange({
// path: currentPath.join('.'),
// oldValue,
// newValue: undefined,
// type: 'delete',
// timestamp: Date.now(),
// });
// return true;
// },
// });
// // Make nested objects observable
// for (const [key, value] of Object.entries(target)) {
// if (typeof value === 'object' && value !== null) {
// target[key] = createProxy(value, [...path, key]);
// }
// }
// watchers.set(target, proxy);
// return proxy;
// }
// return createProxy(obj);
// }
// const watchedDs = createWatchedObject({ _ref: _ds }, async (change) => {
// // console.log('Change detected:', change);
// const { path, newValue } = change;
// try {
// const datasource_changes = {
// [dsSessionP]: {
// ['datasource_main']: {
// watcher: { path, newValue },
// },
// },
// };
// await func.datasource.update(SESSION_ID, datasource_changes);
// } catch (error) {}
// });
//////////////
const clone_ref_value = function (value) {
if (typeof structuredClone === 'function') {
try {
return structuredClone(value);
} catch (error) {}
}
if (typeof _ !== 'undefined' && _.cloneDeep) {
return _.cloneDeep(value);
}
try {
return JSON.parse(JSON.stringify(value));
} catch (error) {
return value;
}
};
// Store snapshots instead of live object references so later datasource
// mutations are visible to xu-ref equality checks and UI refreshes.
let obj = { ds: clone_ref_value(_ds), data: {}, props: clone_ref_value(_ds.in_parameters || {}) };
// The xu-ref snapshot should track app-visible datasource state, not runtime bookkeeping.
// Exclude self-referential stores and refresh metadata so equality checks are driven by real
// data/parameter/UI changes rather than lifecycle ticks.
if (obj.ds) {
delete obj.ds.data_system;
delete obj.ds.refreshed;
delete obj.ds.stat;
delete obj.ds.stat_ts;
if (obj.ds.data_feed) delete obj.ds.data_feed.rows_changed;
if (obj.ds.v) delete obj.ds.v.old_dataSource;
}
try {
const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
obj.data = clone_ref_value(_ds?.data_feed?.rows?.[row_idx]);
} catch (error) {
// error normal if find_ROWID_idx fail
}
let SYS_GLOBAL_OBJ_REFS = _ds_0.data_system['SYS_GLOBAL_OBJ_REFS'];
if ($elm) {
const elm_data = func.runtime.ui.get_data($elm);
const attributes = elm_data?.xuData?.xuPanelProps || elm_data?.xuData?.debug_info?.attribute_stat || {};
obj.attributes = clone_ref_value(attributes);
obj.xu_ui_id = func.runtime.ui.get_attr($elm, 'xu-ui-id');
}
// During datasource refresh, row data can be momentarily empty while the rows are being rebuilt.
// Do not replace a populated ref snapshot with that transient empty state.
const _stored_ref0 = SYS_GLOBAL_OBJ_REFS[ref_field_id];
const _new_data_empty = !obj.data || (typeof obj.data === 'object' && Object.keys(obj.data).length === 0);
const _stored_data_full =
_stored_ref0 && _stored_ref0.data && typeof _stored_ref0.data === 'object' && Object.keys(_stored_ref0.data).length > 0;
if (_new_data_empty && _stored_data_full) {
return ret;
}
// Compare only the parts the new snapshot actually carries. Datasource-path calls have no DOM
// element, so they cannot provide UI attributes or current panel parameter props; render-path calls
// do. Gate those DOM-derived pieces on $elm, while row data remains compared on every call.
const _stored_cmp = SYS_GLOBAL_OBJ_REFS[ref_field_id];
const _has_elm = $elm != null;
const _ref_changed =
!_stored_cmp ||
!xu_isEqual(_stored_cmp.data, obj.data) ||
(_has_elm && !xu_isEqual(_stored_cmp.props, obj.props)) ||
(_has_elm && !xu_isEqual(_stored_cmp.attributes, obj.attributes)) ||
(_has_elm && _stored_cmp.xu_ui_id !== obj.xu_ui_id);
// Temporary instrumentation: logs when a no-$elm props-only diff is ignored by the ref gate.
try {
if (typeof window !== 'undefined' && !_has_elm && _stored_cmp && !_ref_changed && !xu_isEqual(_stored_cmp.props, obj.props)) {
console.log('[xu-propsgate] suppressed no-$elm props-only diff, ref=' + ref_field_id + ' ds=' + (obj?.ds?.dsSession));
}
} catch (e) {}
if (_ref_changed) {
const _ref_was_absent = !SYS_GLOBAL_OBJ_REFS[ref_field_id];
if (_ref_was_absent) {
SYS_GLOBAL_OBJ_REFS[ref_field_id] = {};
}
// SYS_GLOBAL_OBJ_REFS[ref_field_id] = obj;
function deepUpdateObject(a, b) {
for (let key in b) {
if (b.hasOwnProperty(key)) {
if (typeof b[key] === 'object' && b[key] !== null && !Array.isArray(b[key]) && a[key] && typeof a[key] === 'object') {
deepUpdateObject(a[key], b[key]);
} else {
if (!xu_isEqual(a[key], b[key])) {
a[key] = b[key];
}
}
}
}
return a;
}
deepUpdateObject(SYS_GLOBAL_OBJ_REFS[ref_field_id], obj);
// SYS_GLOBAL_OBJ_REFS[ref_field_id] = obj;
ret = true;
}
return ret;
};
// func.UI.create_xu_ref = function (SESSION_ID, dsSessionP, ref_field_id, $elm) {
// let ret;
// const _session = SESSION_OBJ[SESSION_ID];
// let _ds_0 = _session.DS_GLB[0];
// function createWatchedObject(obj, onChange) {
// const watchers = new WeakMap();
// function createProxy(target, path = []) {
// if (watchers.has(target)) {
// return watchers.get(target);
// }
// const proxy = new Proxy(target, {
// set(obj, prop, value) {
// const oldValue = obj[prop];
// let currentPath = [...path, prop];
// // Set the new value
// obj[prop] = value;
// // If the new value is an object, make it observable too
// if (typeof value === 'object' && value !== null) {
// obj[prop] = createProxy(value, currentPath);
// }
// // Notify of change
// // if (oldValue !== value) {
// if (!_.isEqual(value, oldValue)) {
// currentPath.shift();
// onChange({
// path: currentPath.join('.'),
// oldValue,
// newValue: value,
// type: 'set',
// timestamp: Date.now(),
// });
// }
// return true;
// },
// deleteProperty(obj, prop) {
// const oldValue = obj[prop];
// const currentPath = [...path, prop];
// delete obj[prop];
// onChange({
// path: currentPath.join('.'),
// oldValue,
// newValue: undefined,
// type: 'delete',
// timestamp: Date.now(),
// });
// return true;
// },
// });
// // Make nested objects observable
// for (const [key, value] of Object.entries(target)) {
// if (typeof value === 'object' && value !== null) {
// target[key] = createProxy(value, [...path, key]);
// }
// }
// watchers.set(target, proxy);
// return proxy;
// }
// return createProxy(obj);
// }
// let _ds = _session.DS_GLB[dsSessionP];
// // const watchedDs = createWatchedObject({ _ref: _ds }, async (change) => {
// // // console.log('Change detected:', change);
// // const { path, newValue, oldValue } = change;
// // });
// const createBasicProxy = (target) => {
// return new Proxy(target, {
// // Intercept property access
// get(obj, prop) {
// console.log(`Getting property: ${String(prop)}`);
// return prop in obj ? obj[prop] : `Property ${String(prop)} not found`;
// },
// // Intercept property assignment
// set(obj, prop, value) {
// console.log(`Setting property: ${String(prop)} to ${value}`);
// obj[prop] = value;
// return true; // indicates success
// },
// // Intercept property deletion
// deleteProperty(obj, prop) {
// console.log(`Deleting property: ${String(prop)}`);
// if (prop in obj) {
// delete obj[prop];
// return true;
// }
// return false;
// },
// });
// };
// const p = createBasicProxy(_ds);
// // let obj = { ds: watchedDs._ref, data: {}, props: _ds.in_parameters || {} };
// let obj = { ds: {}, data: {}, props: _ds.in_parameters || {} };
// try {
// const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
// obj.data = _ds?.data_feed?.rows?.[row_idx];
// } catch (error) {
// // error normal if find_ROWID_idx fail
// }
// let SYS_GLOBAL_OBJ_REFS = _ds_0.data_system['SYS_GLOBAL_OBJ_REFS'];
// if ($elm) {
// const attributes = $elm?.data()?.xuData?.xuPanelProps || $elm?.data()?.xuData?.debug_info?.attribute_stat || {};
// obj.attributes = attributes;
// obj.xu_ui_id = $elm.attr('xu-ui-id');
// }
// SYS_GLOBAL_OBJ_REFS[ref_field_id] = obj;
// return ret;
// };
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
func.runtime.perf = func.runtime.perf || {};
func.runtime.perf.global_stats = func.runtime.perf.global_stats || { sessions: {} };
func.runtime.perf.get_host = function () {
if (typeof window !== 'undefined') {
return window;
}
if (typeof globalThis !== 'undefined') {
return globalThis;
}
return {};
};
func.runtime.perf.sync_global_ref = function () {
const host = func.runtime.perf.get_host();
host.XUDA_RUNTIME_STATS = func.runtime.perf.global_stats;
return host.XUDA_RUNTIME_STATS;
};
func.runtime.perf.is_enabled = function () {
const host = func.runtime.perf.get_host();
const debug_mode = typeof glb !== 'undefined' && !!glb.DEBUG_MODE;
return !!(debug_mode || host.XUDA_RUNTIME_STATS_ENABLED);
};
func.runtime.perf.now = function () {
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
return performance.now();
}
return Date.now();
};
func.runtime.perf.ensure_session_stats = function (SESSION_ID) {
const global_stats = func.runtime.perf.global_stats;
if (!global_stats.sessions[SESSION_ID]) {
global_stats.sessions[SESSION_ID] = {
created_at: Date.now(),
counters: {},
durations: {},
maps: {},
};
}
if (SESSION_OBJ?.[SESSION_ID]) {
SESSION_OBJ[SESSION_ID].runtime_stats = global_stats.sessions[SESSION_ID];
}
func.runtime.perf.sync_global_ref();
return global_stats.sessions[SESSION_ID];
};
func.runtime.perf.reset_session = function (SESSION_ID) {
if (!SESSION_ID) {
return false;
}
delete func.runtime.perf.global_stats.sessions[SESSION_ID];
if (SESSION_OBJ?.[SESSION_ID]) {
delete SESSION_OBJ[SESSION_ID].runtime_stats;
}
func.runtime.perf.sync_global_ref();
return true;
};
func.runtime.perf.reset = function () {
func.runtime.perf.global_stats = { sessions: {} };
const session_ids = Object.keys(SESSION_OBJ || {});
for (let index = 0; index < session_ids.length; index++) {
delete SESSION_OBJ[session_ids[index]].runtime_stats;
}
func.runtime.perf.sync_global_ref();
return func.runtime.perf.global_stats;
};
func.runtime.perf.get_session_stats = function (SESSION_ID) {
if (!SESSION_ID) {
return null;
}
return func.runtime.perf.global_stats.sessions[SESSION_ID] || null;
};
func.runtime.perf.increment = function (SESSION_ID, key, amount = 1) {
if (!func.runtime.perf.is_enabled()) {
return 0;
}
const stats = func.runtime.perf.ensure_session_stats(SESSION_ID);
stats.counters[key] = (stats.counters[key] || 0) + amount;
return stats.counters[key];
};
func.runtime.perf.increment_map = function (SESSION_ID, bucket, key, amount = 1) {
if (!func.runtime.perf.is_enabled() || typeof key === 'undefined' || key === null) {
return 0;
}
const stats = func.runtime.perf.ensure_session_stats(SESSION_ID);
if (!stats.maps[bucket]) {
stats.maps[bucket] = {};
}
const map_key = key.toString();
stats.maps[bucket][map_key] = (stats.maps[bucket][map_key] || 0) + amount;
return stats.maps[bucket][map_key];
};
func.runtime.perf.record_duration = function (SESSION_ID, key, duration_ms) {
if (!func.runtime.perf.is_enabled()) {
return null;
}
const stats = func.runtime.perf.ensure_session_stats(SESSION_ID);
if (!stats.durations[key]) {
stats.durations[key] = {
count: 0,
total_ms: 0,
max_ms: 0,
last_ms: 0,
avg_ms: 0,
};
}
const metric = stats.durations[key];
metric.count++;
metric.total_ms += duration_ms;
metric.last_ms = duration_ms;
metric.max_ms = Math.max(metric.max_ms, duration_ms);
metric.avg_ms = metric.total_ms / metric.count;
return metric;
};
func.runtime.perf.start = function (SESSION_ID, key) {
if (!func.runtime.perf.is_enabled()) {
return null;
}
const started_at = func.runtime.perf.now();
return function () {
return func.runtime.perf.record_duration(SESSION_ID, key, func.runtime.perf.now() - started_at);
};
};
func.runtime.ui.init_screen = async function (options) {
const {
SESSION_ID,
prog_id,
sourceScreenP,
callingDataSource_objP,
$callingContainerP,
triggerIdP,
rowIdP,
jobNoP,
is_panelP,
parameters_obj_inP,
source_functionP,
call_screen_propertiesP,
refreshed_ds,
parameters_raw_obj,
} = options;
if (!prog_id) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_RND_030',
source: 'func.runtime.ui.init_screen',
message: 'program is empty',
type: 'E',
});
return null;
}
const screen_ret = await func.utils.get_screen_obj(SESSION_ID, prog_id);
if (!screen_ret) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_RND_030',
source: 'func.runtime.ui.init_screen',
message: 'program is not a screen object',
type: 'E',
details: {
prog_id,
},
});
return null;
}
await func.UI.utils.init_ui_framework(SESSION_ID, prog_id);
const _session = SESSION_OBJ[SESSION_ID];
const screenInfo = structuredClone(screen_ret);
const ssr_payload = func.runtime.render.should_use_ssr_payload(SESSION_ID, { prog_id }) ? func.runtime.render.get_ssr_payload(_session) : null;
const screen_type = source_functionP?.split('_')?.[1];
const screenId = ssr_payload?.screenId || (glb.screen_num++).toString();
if (SCREEN_BLOCKER_OBJ[prog_id + (sourceScreenP ? '_' + sourceScreenP : '')]) {
const wait_for_SCREEN_BLOCKER_release = function () {
return new Promise((resolve) => {
const interval = setInterval(function () {
if (!SCREEN_BLOCKER_OBJ[prog_id + (sourceScreenP ? '_' + sourceScreenP : '')]) {
clearInterval(interval);
resolve();
}
}, 5);
});
};
await wait_for_SCREEN_BLOCKER_release();
}
func.UI.utils.screen_blocker(true, prog_id + (sourceScreenP ? '_' + sourceScreenP : ''));
if ($callingContainerP && !xu_isEmpty($callingContainerP)) {
const calling_data = func.runtime.ui.get_data($callingContainerP);
if (calling_data?.xuData) calling_data.xuData.screenInfo = screenInfo;
}
let $dialogDiv;
let $rootFrame;
const params = {
prog_id,
sourceScreenP,
$callingContainerP,
triggerIdP,
callingDataSource_objP,
rowIdP,
renderType: screenInfo.properties?.renderType,
parameters_obj_inP,
source_functionP,
is_panelP,
screen_type,
screenInfo,
call_screen_propertiesP,
parentDataSourceNoP: _session.DS_GLB?.[callingDataSource_objP?.dsSession]?.dsSession || callingDataSource_objP?.parentDataSourceNo || 0,
parameters_raw_obj,
containerIdP: ssr_payload?.containerId || null,
ssr_payload,
};
const screen_host = func.runtime.ui.create_screen_host(SESSION_ID, screen_type, params, $callingContainerP, screenId);
$dialogDiv = screen_host.$dialogDiv;
$rootFrame = screen_host.$rootFrame;
params.containerIdP = func.runtime.ui.get_attr($rootFrame, 'id');
params.$container = $rootFrame;
const containerId = 'container_' + params.screenInfo.properties?.id + '_' + screenId;
const data = {
note: ' ROOT container',
root: true,
screenId,
is_panelP,
prog_id,
screen_type,
container: '#' + containerId,
};
if (is_panelP) {
func.runtime.ui.get_data($rootFrame).xuData.rootFrame = data;
} else {
const rf_data = func.runtime.ui.get_data($rootFrame);
if (!rf_data?.xuData) {
func.runtime.ui.set_data($rootFrame, 'xuData', {});
}
func.runtime.ui.set_attr($rootFrame, 'id', containerId);
func.runtime.ui.get_data($rootFrame).xuData.rootFrame = data;
func.runtime.ui.set_style($rootFrame, 'display', 'contents');
}
if (screen_host.reused_ssr_host && func.runtime.render.is_takeover_mode(_session)) {
func.runtime.ui.empty($rootFrame);
}
if (!is_panelP) func.UI.utils.indicator.screen.busy();
const ret = await func.datasource.create(
SESSION_ID,
prog_id,
refreshed_ds,
params.parentDataSourceNoP,
func.runtime.ui.get_attr($rootFrame, 'id'),
rowIdP,
jobNoP,
null,
parameters_raw_obj,
null,
null,
null,
null,
is_panelP,
parameters_obj_inP,
);
const _ds = SESSION_OBJ[SESSION_ID].DS_GLB[ret.dsSessionP];
_ds.screen_params = params;
params.dsSessionP = ret.dsSessionP;
func.runtime.ui.update_sys_obj_win_info(SESSION_ID, params.dsSessionP);
if (ret.dsSessionP < 0) {
return;
}
let viewDoc;
const view_ret = await func.utils.VIEWS_OBJ.get(SESSION_ID, SESSION_OBJ[SESSION_ID].DS_GLB[ret.dsSessionP].prog_id);
if (view_ret) {
viewDoc = view_ret;
}
if (!viewDoc?.progUi) {
return func.utils.alerts.invoke(SESSION_ID, 'system_msg', 'SYS_MSG_0780', params.renderType, ret.dsSessionP);
}
let node = structuredClone(viewDoc.progUi);
if (!node.length) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_RND_040',
source: 'func.runtime.ui.render_single_view_node',
message: 'ui node empty',
type: 'W',
details: {
prog_id: SESSION_OBJ[SESSION_ID].DS_GLB[ret.dsSessionP].prog_id,
},
});
return null;
}
const ret_render_$container = await func.runtime.render.render_ui_tree(
SESSION_ID,
$rootFrame,
node[0],
null,
params,
jobNoP,
null,
null,
null,
null,
null,
$rootFrame,
);
if (!is_panelP) func.UI.utils.indicator.screen.normal();
return await func.runtime.ui.screen_loading_done({
SESSION_ID,
paramsP: params,
$div: ret_render_$container,
jobNoP,
});
};
func.runtime.ui.update_sys_obj_win_info = function (SESSION_ID, dsNoP) {
const _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsNoP];
if (!_ds) return;
if (!_ds.data_system) {
_ds.data_system = {};
}
_ds.data_system['SYS_STR_WIN_ID'] = _ds.tree_obj?.id;
_ds.data_system['SYS_STR_WIN_NAME'] = _ds.tree_obj?.menuName;
if (SESSION_OBJ[SESSION_ID].DS_GLB[dsNoP].mode) {
_ds.data_system['SYS_STR_WIN_MODE'] = SESSION_OBJ[SESSION_ID].DS_GLB[dsNoP].mode;
}
};
func.runtime.ui.validate_exit_events = async function (SESSION_ID, div_data_paramsP, forceP) {
return new Promise(async (resolve) => {
await func.events.validate(SESSION_ID, 'on_exit', div_data_paramsP.dsSessionP, null, 'screen');
const interval = setInterval(function () {
if (!SESSION_OBJ[SESSION_ID].WORKER_OBJ.jobs.length || forceP) {
clearInterval(interval);
resolve();
}
}, 5);
});
};
func.runtime.ui.call_embed = function (SESSION_ID, prog) {
const $embed = func.runtime.ui.get_embed_container(SESSION_ID);
func.runtime.ui.empty($embed);
const embed_data = func.runtime.ui.get_data($embed);
if (embed_data?.xuData) {
embed_data.xuData.screenInfo = null;
}
for (const [key, val] of Object.entries(SESSION_OBJ[SESSION_ID].DS_GLB)) {
if (key) func.datasource.del(SESSION_ID, key);
}
func.UI.main.embed_prog_execute(SESSION_ID, prog);
};
// Browser UI/screen boot helpers remain here. DOM/host helpers moved to xuda_runtime.browser.dom.js,
// presentation helpers moved to xuda_runtime.browser.presentation.js,
// refresh/realtime helpers moved to xuda_runtime.browser.refresh.js, and render/widget helpers moved to xuda_runtime.browser.render.js.
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only nav, modal, and page controller helpers live here so host/container wiring can stay focused.
func.runtime.ui.ensure_modal_controller = function (SESSION_ID, modal_id) {
var modal_controller = document.querySelector('xu-modal-controller');
if (!modal_controller) {
func.UI.component.create_app_modal_component(SESSION_ID, modal_id);
modal_controller = document.querySelector('xu-modal-controller');
}
return modal_controller;
};
func.runtime.ui.get_controller_params = function (controller_element) {
return func.runtime.ui.get_data(controller_element, 'xuControllerParams') || {};
};
func.runtime.ui.set_controller_params = function (controller_element, params) {
func.runtime.ui.set_data(controller_element, 'xuControllerParams', params);
return params;
};
func.runtime.ui.get_nav = function (SESSION_ID) {
return func.runtime.ui.find_in_root(SESSION_ID, 'xu-nav');
};
func.runtime.ui.ensure_nav = function (SESSION_ID, $container) {
let nav = func.runtime.ui.get_nav(SESSION_ID);
if (nav && nav.length) {
return nav;
}
var nav_el = document.createElement('xu-nav');
func.runtime.ui.append($container, nav_el);
var $nav = func.runtime.ui._wrap_matches([nav_el]);
func.UI.component.init_xu_nav($container, $nav);
return $nav;
};
func.runtime.ui.get_page_component_name = function (dsSessionP) {
return 'xu-page-component-' + dsSessionP;
};
func.runtime.ui.resolve_screen_property = async function (SESSION_ID, paramsP, property) {
var property_value = paramsP?.screenInfo?.properties?.[property] || paramsP?.screenInfo?.properties?.frameworkProperties?.[property];
if (paramsP?.call_screen_propertiesP) {
if (paramsP.call_screen_propertiesP?.[property]) {
property_value = paramsP.call_screen_propertiesP[property];
}
if (paramsP.call_screen_propertiesP[`xu-exp:${property}`]) {
property_value = (await func.expression.get(SESSION_ID, paramsP.call_screen_propertiesP[`xu-exp:${property}`], paramsP.dsSessionP, property)).result;
}
}
return property_value;
};
func.runtime.ui.apply_framework_properties = async function (SESSION_ID, paramsP, ui_framework, params) {
params.properties = {};
params.properties['name'] = await func.runtime.ui.resolve_screen_property(SESSION_ID, paramsP, 'menuTitle');
const properties = await ui_framework?.properties?.();
if (!properties) {
return params.properties;
}
for await (const [key, val] of Object.entries(properties)) {
params.properties[key] = await func.runtime.ui.resolve_screen_property(SESSION_ID, paramsP, key);
}
return params.properties;
};
func.runtime.ui.build_modal_params = function (paramsP, $div, $container, close_callback) {
return {
screenId: paramsP.screenId,
$dialogDiv: func.runtime.ui.get_children($div),
$container: $container,
dsSession: paramsP.dsSessionP,
modal_id: 'app_modal-' + paramsP.dsSessionP.toString(),
screenInfo: paramsP.screenInfo,
close_callback,
paramsP,
};
};
func.runtime.ui.set_modal_params = function (SESSION_ID, modal_id, params) {
var xu_modal_controller = func.runtime.ui.ensure_modal_controller(SESSION_ID, modal_id);
var controller_params = func.runtime.ui.get_controller_params(xu_modal_controller);
controller_params[modal_id] = params;
func.runtime.ui.set_controller_params(xu_modal_controller, controller_params);
return xu_modal_controller;
};
func.runtime.ui.get_modal_params = function (SESSION_ID, modal_id) {
const xu_modal_controller = func.runtime.ui.ensure_modal_controller(SESSION_ID, modal_id);
return func.runtime.ui.get_controller_params(xu_modal_controller)[modal_id];
};
func.runtime.ui.set_modal_instance = function (modal_id, modal) {
APP_MODAL_OBJ[modal_id] = modal;
return modal;
};
func.runtime.ui.has_modal_instance = function (modal_id) {
return !!APP_MODAL_OBJ[modal_id];
};
func.runtime.ui.delete_modal_instance = function (modal_id) {
delete APP_MODAL_OBJ[modal_id];
return true;
};
func.runtime.ui.close_all_modals = function () {
for (const [key, val] of Object.entries(APP_MODAL_OBJ)) {
if (val) {
UI_FRAMEWORK_PLUGIN.modal.close(key);
}
}
return true;
};
func.runtime.ui.build_popover_params = function (paramsP, $div, $container) {
return {
menuTitle: paramsP.screenInfo.properties?.menuTitle,
screenId: paramsP.screenId,
$dialogDiv: func.runtime.ui.get_children($div),
$container: $container,
};
};
func.runtime.ui.build_page_params = function (SESSION_ID, paramsP, $div_content, $container, nav) {
return {
div: $div_content,
name: paramsP.screenInfo.properties?.menuTitle,
screenId: paramsP.screenId,
$container: $container,
dsSession: paramsP.dsSessionP,
SESSION_ID,
nav,
paramsP,
};
};
func.runtime.ui.get_nav_data = function ($nav) {
return func.runtime.ui.get_data($nav)?.xuData || null;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
// Browser-only screen presentation helpers live here so the browser runtime can be split by concern.
func.runtime.ui.ensure_nav_params_registry = function ($nav) {
const nav_data = func.runtime.ui.get_nav_data($nav);
if (!nav_data) {
return null;
}
if (!nav_data.nav_params) {
nav_data.nav_params = {};
}
return nav_data.nav_params;
};
func.runtime.ui.restore_page_validate_state = function ($nav, dsSessionP, $container) {
const nav_data = func.runtime.ui.get_nav_data($nav);
const existing_params = nav_data?.params?.[dsSessionP];
if (func.runtime.ui.get_data(existing_params?.$container)?.xuData?.validate_screen_ready) {
func.runtime.ui.get_data($container).xuData.validate_screen_ready = func.runtime.ui.get_data(existing_params.$container).xuData.validate_screen_ready;
return true;
}
return false;
};
func.runtime.ui.register_page_params = function ($nav, dsSessionP, params) {
const nav_params = func.runtime.ui.ensure_nav_params_registry($nav);
if (!nav_params) {
return false;
}
nav_params[dsSessionP] = params;
return true;
};
func.runtime.ui.get_root_component_name = function (SESSION_ID) {
return 'xu-root-component-' + SESSION_ID;
};
func.runtime.ui.attach_nav_root_div = function ($nav, $div_content) {
const nav_data = func.runtime.ui.get_nav_data($nav);
if (!nav_data) {
return false;
}
nav_data.$div = $div_content;
return true;
};
func.runtime.ui.set_nav_root = async function ($nav, SESSION_ID) {
const nav_element = func.runtime.ui.get_first_node($nav);
if (!nav_element?.setRoot) {
return false;
}
await nav_element.setRoot(func.runtime.ui.get_root_component_name(SESSION_ID));
return true;
};
func.runtime.ui.release_screen_blocker = function (paramsP) {
func.UI.utils.screen_blocker(false, paramsP.prog_id + '_' + paramsP.sourceScreenP);
return true;
};
func.runtime.ui.close_modal_session = async function (SESSION_ID, modal_id) {
func.runtime.ui.delete_modal_instance(modal_id);
const params = func.runtime.ui.get_modal_params(SESSION_ID, modal_id);
if (params && params.$container) {
await func.runtime.ui.validate_exit_events(SESSION_ID, func.runtime.ui.get_data(params.$container)?.xuData?.paramsP, null);
func.datasource.clean_all(SESSION_ID, params.dsSession);
}
return true;
};
func.runtime.ui.render_screen_type = async function (options) {
const $div_content = func.runtime.ui.get_children(options.$div);
func.runtime.ui.sync_child_parent_container(options.$div);
const assert_framework_screen_supported = function (screen_type) {
if (!func.runtime.session.is_slim(options.SESSION_ID)) {
return;
}
throw new Error('Slim mode does not support "' + screen_type + '" screens without a UI framework plugin');
};
let $ret = options.$div;
let $nav = func.runtime.ui.get_nav(options.SESSION_ID);
let params;
switch (options.paramsP.screen_type) {
case 'modal': {
assert_framework_screen_supported('modal');
params = func.runtime.ui.build_modal_params(options.paramsP, options.$div, options.$container, options.close_modal);
const modal_id = params.modal_id;
func.runtime.ui.set_modal_params(options.SESSION_ID, modal_id, params);
const modalController = await new UI_FRAMEWORK_PLUGIN.modal();
await func.runtime.ui.apply_framework_properties(options.SESSION_ID, options.paramsP, modalController, params);
if (!func.runtime.ui.has_modal_instance(modal_id)) {
const modal = await modalController.create(params);
func.runtime.ui.set_modal_instance(modal_id, modal);
} else {
func.runtime.ui.empty(document.querySelector(modal_id));
}
await modalController.init(params);
break;
}
case 'popover': {
assert_framework_screen_supported('popover');
const xu_popover_controller = func.UI.component.create_app_popover_component(options.SESSION_ID);
params = func.runtime.ui.build_popover_params(options.paramsP, options.$div, options.$container);
func.runtime.ui.set_data(xu_popover_controller, 'xuControllerParams', params);
const popover = new UI_FRAMEWORK_PLUGIN.popover(options.SESSION_ID);
await func.runtime.ui.apply_framework_properties(options.SESSION_ID, options.paramsP, popover, params);
await popover.open(params);
CURRENT_APP_POPOVER = popover;
func.runtime.ui.release_screen_blocker(options.paramsP);
break;
}
case 'page': {
assert_framework_screen_supported('page');
const nav = func.runtime.ui.get_first_node($nav);
params = func.runtime.ui.build_page_params(options.SESSION_ID, options.paramsP, $div_content, options.$container, nav);
const component_name = func.runtime.ui.get_page_component_name(options.paramsP.dsSessionP);
func.runtime.ui.ensure_nav_params_registry($nav);
func.runtime.ui.restore_page_validate_state($nav, options.paramsP.dsSessionP, params.$container);
if (!func.runtime.ui.get_nav_data($nav)) return;
func.runtime.ui.register_page_params($nav, options.paramsP.dsSessionP, params);
if (!document.querySelector(component_name)) {
await func.UI.component.create_app_page_component(options.SESSION_ID, options.paramsP.dsSessionP);
const page = new UI_FRAMEWORK_PLUGIN.page();
await func.runtime.ui.apply_framework_properties(options.SESSION_ID, options.paramsP, page, params);
await page.create(params);
await page.init(params);
nav.push(component_name, { params });
} else {
console.error('[xuda-runtime] xuda_runtime.browser.presentation.js:134');
func.runtime.ui.empty(component_name);
await UI_FRAMEWORK_PLUGIN.page(options.SESSION_ID, options.paramsP.dsSessionP);
}
func.runtime.ui.get_data(options.$div).xuData.paramsP = func.runtime.ui.get_data(options.$container).xuData.paramsP;
break;
}
case 'panel':
func.runtime.ui.append(options.$container, $div_content);
$ret = options.$container;
break;
default:
$nav = func.runtime.ui.ensure_nav(options.SESSION_ID, options.$container);
func.runtime.ui.attach_nav_root_div($nav, $div_content);
await func.runtime.ui.set_nav_root($nav, options.SESSION_ID);
$ret = options.$container;
break;
}
return $ret;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
// Browser-only non-panel view renderers live here so screen presentation orchestration can stay focused.
func.runtime.ui.ensure_container_attributes = async function (options) {
const container_data = func.runtime.ui.get_data(options.$container);
if (!xu_isEmpty(container_data?.xuAttributes)) {
return options.$container;
}
await func.runtime.render.set_attributes_new({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
$elm: options.$container,
is_init: true,
});
return options.$container;
};
func.runtime.ui.copy_runtime_state = function ($source, $target) {
const source_data = func.runtime.ui.get_data($source);
const target_data = func.runtime.ui.get_data($target);
if (!source_data?.xuData || !target_data?.xuData || !source_data?.xuAttributes || !target_data?.xuAttributes) {
return $target;
}
const xu_data_keys = Object.keys(source_data.xuData);
for (let index = 0; index < xu_data_keys.length; index++) {
const key = xu_data_keys[index];
const val = source_data.xuData[key];
try { target_data.xuData[key] = structuredClone(val); } catch (_) { target_data.xuData[key] = val; }
}
const xu_attribute_keys = Object.keys(source_data.xuAttributes);
for (let index = 0; index < xu_attribute_keys.length; index++) {
const key = xu_attribute_keys[index];
const val = source_data.xuAttributes[key];
try { target_data.xuAttributes[key] = structuredClone(val); } catch (_) { target_data.xuAttributes[key] = val; }
}
func.runtime.ui.mark_refresh_index_dirty_from_target?.($target, target_data.xuData.SESSION_ID || source_data.xuData.SESSION_ID);
return $target;
};
func.runtime.ui.ensure_multi_view_state = function ($div, nodeP, $container) {
const div_data = func.runtime.ui.get_data($div);
const container_data = func.runtime.ui.get_data($container);
if (!div_data?.xuData?.node || !div_data.xuData.node.children) {
div_data.xuData.node = nodeP;
}
if (!div_data?.xuData?.debug_info) {
div_data.xuData.debug_info = {
id: nodeP.id,
parent_id: container_data?.xuData?.ui_id,
};
}
return $div;
};
func.runtime.ui.should_close_mobile_overlays = function (paramsP) {
return !REFRESHER_IN_PROGRESS && (paramsP.is_mobile_popover || paramsP.is_mobile_page);
};
func.runtime.ui.render_single_view_node = async function (options) {
const exist_elm_obj = func.runtime.render.find_existing_element({
$container: options.$container,
nodeP: options.nodeP,
keyP: options.keyP,
render_context: options.render_context,
});
let $div = exist_elm_obj.div;
if (!$div) {
const $wrapper = document.createElement('div');
$div = await func.runtime.ui.create_container({
SESSION_ID: options.SESSION_ID,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
treeP: options.treeP,
parent_nodeP: options.parent_nodeP,
prop: options.prop,
div_typeP: 'div',
$appendToP: $wrapper,
attr_str: '',
});
if (!$div) return;
if (func.runtime.ui.should_close_mobile_overlays(options.paramsP)) {
func.runtime.ui.close_all_modals();
}
const div_el = func.runtime.ui.get_first_node($div);
if (div_el) {
div_el.addEventListener('mouseenter', function (e) {
options.hover_handlers.hover_in($div, e);
});
div_el.addEventListener('mouseleave', function () {
options.hover_handlers.hover_out();
});
}
}
await options.iterate_child($div, options.nodeP, null, $div);
await func.runtime.ui.ensure_container_attributes({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
});
func.runtime.ui.copy_runtime_state($div, options.$container);
return await func.runtime.ui.render_screen_type({
SESSION_ID: options.SESSION_ID,
$div,
$container: options.$container,
paramsP: options.paramsP,
close_modal: options.close_modal,
});
};
func.runtime.ui.render_multi_view_node = async function (options) {
const $div = options.$container;
func.runtime.ui.ensure_multi_view_state($div, options.nodeP, options.$container);
const done = async function (continuous_idx) {
await func.runtime.ui.ensure_container_attributes({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
});
return await func.runtime.ui.render_screen_type({
SESSION_ID: options.SESSION_ID,
$div,
$container: options.$container,
paramsP: options.paramsP,
close_modal: options.close_modal,
});
};
if (func.runtime.ui.should_close_mobile_overlays(options.paramsP)) {
func.runtime.ui.close_all_modals();
}
const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[options.paramsP.dsSessionP];
if (!_ds.data_feed || xu_isEmpty(_ds.data_feed.rows)) {
await func.events.validate(options.SESSION_ID, 'record_not_found', options.paramsP.dsSessionP);
return await done(null);
}
// B1 — no-op refresh suppression: when a datasource refresh produced rows identical to the
// pre-refresh snapshot AND this container already holds rendered rows, keep the existing DOM.
// Rebuilding tears down and re-creates the entire list (visible flick, multi-second freeze,
// an abandoned child datasource session per refresh) for zero data change. Field-level
// changes still flow through the normal update/diff path; a genuinely-changed row set (any
// _ROWID or value difference, or a fresh/empty container) renders exactly as before.
try {
const _prev_rows = _ds.__refresh_prev_rows;
delete _ds.__refresh_prev_rows;
if (Array.isArray(_prev_rows)) {
const _cur_rows = _ds.data_feed.rows || [];
const _existing_children = func.runtime.ui.get_children(options.$container);
const _container_node = func.runtime.ui.get_first_node(options.$container);
const _rows_unchanged =
_container_node &&
_container_node.isConnected &&
_existing_children.length > 0 &&
_prev_rows.length === _cur_rows.length &&
_cur_rows.every(function (row, row_index) {
const prev = _prev_rows[row_index];
return prev && prev._ROWID === row._ROWID && xu_isEqual(prev, row);
});
if (_rows_unchanged) {
return await done(null);
}
}
} catch (e) {}
const rows = _ds.data_feed.rows || [];
for (let row_index = 0; row_index < rows.length; row_index++) {
const val = rows[row_index];
const node = JSON.parse(JSON.stringify(options.nodeP));
_ds.currentRecordId = val._ROWID;
await options.iterate_child($div, node, { continuous_idx: null }, options.$root_container);
await func.runtime.ui.ensure_container_attributes({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
});
}
return await done(null);
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
// Browser-only panel rendering helpers live here so generic view rendering can stay focused.
func.runtime.ui.render_panel_node = async function (options) {
const $wrapper = document.createElement('div');
const $div = await func.runtime.ui.create_container({
SESSION_ID: options.SESSION_ID,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
treeP: options.treeP,
parent_nodeP: options.parent_nodeP,
prop: options.prop,
$appendToP: $wrapper,
attr_str: '',
});
let ret = await func.runtime.render.set_attributes_new({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
$elm: $div.cloneNode(true),
is_init: true,
refreshed_ds: options.refreshed_ds,
});
if (ret.abort) {
const _tpl = document.createElement('template');
_tpl.appendChild($div);
return (ret.$new_div = _tpl);
}
let $ret_panel_div = ret.$new_div;
if (!func.runtime.ui.get_first_node($ret_panel_div)?.childElementCount && options.nodeP.children.length) {
$ret_panel_div = await func.runtime.render.render_ui_tree(
options.SESSION_ID,
options.$container,
options.nodeP.children[0],
options.parent_infoP,
options.paramsP,
options.jobNoP,
null,
0,
null,
options.nodeP,
null,
options.$root_container,
);
}
const container_data = func.runtime.ui.get_data(options.$container);
if (!container_data?.xuData?.paramsP) {
return options.$container;
}
const $div_items = func.runtime.ui.get_data($div)?.xuData?.node?.children;
await func.runtime.ui.panel_post_render_handler({
SESSION_ID: options.SESSION_ID,
$container: options.$container,
$wrapper: $ret_panel_div,
nodeP: options.nodeP,
$panel_div: $div,
jobNoP: options.jobNoP,
});
if (container_data?.xuData?.node) {
container_data.xuData.node.children = $div_items;
}
return options.$container;
};
func.runtime.ui.panel_post_render_handler = async function (options) {
try {
const container_data = func.runtime.ui.get_data(options.$container);
const wrapper_data = func.runtime.ui.get_data(options.$wrapper);
const parent_container_data = func.runtime.ui.get_data(func.runtime.ui.get_parent(options.$container));
const _container_ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[container_data?.xuData?.paramsP?.dsSessionP];
const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[wrapper_data?.xuData?.paramsP?.dsSessionP];
const panel_wrapper_id = container_data?.xuSkipPanelReplacement ? null : container_data?.xuPanelData?.xu_panel_xu_ui_id;
const find_old_panels_elements = function () {
if (!panel_wrapper_id) {
return func.runtime.ui._wrap_matches([]);
}
return func.runtime.ui.find_panel_wrapper_in_root(options.SESSION_ID, panel_wrapper_id);
};
const $old_panel_div = find_old_panels_elements();
const wrapper_children = func.runtime.ui.get_children(options.$wrapper);
const set_xuPanelData_to_the_new_rendered_items = function () {
container_data.xuPanelWrapper = { isWrapper: true, panelXuAttributes: { ...wrapper_data?.xuAttributes }, panelDivData: { ...wrapper_data } };
func.runtime.ui.set_attr(options.$container, 'xu-panel-wrapper-id', func.runtime.ui.get_attr(options.$wrapper, 'xu-ui-id'));
for (let child_index = 0; child_index < wrapper_children.length; child_index++) {
const val = wrapper_children[child_index];
const val_data = func.runtime.ui.get_data(val);
if (!val_data.xuPanelData) {
val_data.xuPanelData = {};
}
val_data.xuPanelData.parent_element_ui_id = $old_panel_div?.length ? parent_container_data?.xuData?.ui_id : container_data?.xuData?.ui_id;
val_data.xuPanelData.xu_panel_xu_ui_id = (options.nodeP.xu_tree_id || options.nodeP.id) + '-' + _container_ds?.currentRecordId;
val_data.xuPanelData.node = options.nodeP;
val_data.xuPanelData.$panel_div = options.$panel_div.cloneNode(true);
}
};
set_xuPanelData_to_the_new_rendered_items();
if ($old_panel_div?.length) {
func.runtime.ui.get_first_node($old_panel_div).after(...wrapper_children);
} else {
const existing_children = func.runtime.ui.get_children(options.$container);
const existing_children_by_elem_key = {};
for (let existing_index = 0; existing_index < existing_children.length; existing_index++) {
const elm = existing_children[existing_index];
const elem_key = func.runtime.ui.get_data(elm)?.xuData?.elem_key;
if (elem_key) {
existing_children_by_elem_key[elem_key] = elm;
}
}
for (let child_index = 0; child_index < wrapper_children.length; child_index++) {
const child = wrapper_children[child_index];
const elem_key = func.runtime.ui.get_data(child)?.xuData?.elem_key;
const existing_child = elem_key ? existing_children_by_elem_key[elem_key] : null;
if (existing_child) {
// Positional replacement: the re-rendered child must take the OLD child's slot.
// remove(old)+append(new) pushed the replacement to the container END, so a
// datasource multi-view list (e.g. the daf pager) rendered its current record last —
// the record id decides WHICH element to replace, never WHERE it goes.
func.runtime.ui.insert_before(child, existing_child);
func.runtime.ui.remove(existing_child);
} else {
// Order-aware insertion: in a datasource multi-view, the CURRENT record's panel renders
// out-of-band (its panel identity nodeId-currentRecordId already exists), so its element
// arrives AFTER its siblings; a blind append parks it at the END of the list (current
// record rendered last — e.g. the daf pager). Derive the slot from the datasource row
// order instead: the record id decides WHICH element this is, row order decides WHERE.
const child_record_id = func.runtime.ui.get_data(child)?.xuData?.recordid;
const order_rows = (function () {
if (!child_record_id) return null;
const containing = function (rows) {
return Array.isArray(rows) && rows.length > 1 && rows.some((row) => row._ROWID === child_record_id) ? rows : null;
};
return containing(_container_ds?.data_feed?.rows) || containing(_ds?.data_feed?.rows);
})();
const row_idx_of = function (record_id) {
if (!record_id || !order_rows) return -1;
for (let row_index = 0; row_index < order_rows.length; row_index++) {
if (order_rows[row_index]._ROWID === record_id) return row_index;
}
return -1;
};
const child_row_idx = row_idx_of(child_record_id);
let $insert_before_sibling = null;
if (child_row_idx >= 0) {
const current_children = func.runtime.ui.get_children(options.$container);
for (let sibling_index = 0; sibling_index < current_children.length; sibling_index++) {
const sibling_row_idx = row_idx_of(func.runtime.ui.get_data(current_children[sibling_index])?.xuData?.recordid);
if (sibling_row_idx > child_row_idx) {
$insert_before_sibling = current_children[sibling_index];
break;
}
}
}
if ($insert_before_sibling) {
func.runtime.ui.insert_before(child, $insert_before_sibling);
} else {
func.runtime.ui.append(options.$container, child);
}
}
}
}
if (!wrapper_data?.xuData?.dsSession) {
return options.jobNoP;
}
if ($old_panel_div?.length) {
if (parent_container_data?.xuData?.paramsP) {
parent_container_data.xuData.paramsP.dsSessionP = _ds.parentDataSourceNo;
}
} else {
if (container_data?.xuData?.paramsP) {
container_data.xuData.paramsP.dsSessionP = _ds.parentDataSourceNo;
}
}
if ($old_panel_div?.length) {
func.runtime.ui.remove($old_panel_div);
}
return options.jobNoP;
} catch (error) {
return options.jobNoP;
}
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
// Browser-only screen-ready event flow lives here so loading lifecycle can stay focused.
func.runtime.ui.refresh_screen_ready_fields = async function (options) {
const current_record_id = options._ds?.currentRecordId;
const changed_fields = options._ds?.data_feed?.form_fields_changed?.[current_record_id];
if (!changed_fields) {
return false;
}
const fields_to_refresh = Object.keys(changed_fields);
const containerId = options._ds.containerId;
const container = func.runtime.ui.find_element_by_id(containerId);
if (container) {
container.dispatchEvent(new CustomEvent(containerId + '.refresh', { detail: ['init', fields_to_refresh] }));
}
if (options.$div_objP) {
await func.runtime.ui.refresh_xu_attributes({
SESSION_ID: options.SESSION_ID,
fields_arr: fields_to_refresh,
$elm_to_search: options.$div_objP,
dsSession_changed: options.paramsP.dsSessionP,
});
}
return true;
};
func.runtime.ui.collect_screen_ready_events = async function (options) {
const _prog = await func.utils.VIEWS_OBJ.get(options.SESSION_ID, options._ds.prog_id);
const viewEventExec_arr = [];
if (!_prog?.progEvents || xu_isEmpty(_prog.progEvents)) {
return viewEventExec_arr;
}
for await (const event_obj of _prog.progEvents) {
if (event_obj.data.type !== 'screen_ready' || xu_isEmpty(event_obj.workflow)) {
continue;
}
if (event_obj.data.condition) {
const res = await func.expression.get(options.SESSION_ID, event_obj.data.condition, options.paramsP.dsSessionP, 'condition', options.paramsP.rowIdP, null, null, null, null, event_obj);
if (!res.result) {
continue;
}
}
for await (const trigger_obj of event_obj.workflow) {
if (!trigger_obj.data.enabled) {
continue;
}
const expression = trigger_obj.props.condition || undefined;
if (expression) {
const expCond = await func.expression.get(options.SESSION_ID, expression, options.paramsP.dsSessionP, 'condition', options.paramsP.rowIdP, trigger_obj.data.type);
if (!expCond.result) {
continue;
}
}
if (!trigger_obj.data.action) {
func.utils.debug_report(options.SESSION_ID, 'collect_screen_ready_events', `Error initiating screen_ready prog: ${options._ds.viewSourceDesc} reason: missing action`, 'E');
break;
}
if (!glb.REFERENCE_LESS_FUNCTIONS.includes(trigger_obj.data.action) && !trigger_obj.data.name?.prog) {
func.utils.debug_report(options.SESSION_ID, 'collect_screen_ready_events', `Error initiating screen_ready prog: ${options._ds.viewSourceDesc} reason: missing reference`, 'E');
break;
}
viewEventExec_arr.push({
eventInfo: trigger_obj,
eventId: event_obj.id,
triggerId: trigger_obj.id,
expression,
});
}
}
return viewEventExec_arr;
};
func.runtime.ui.execute_screen_ready_events = async function (options) {
const _ds = SESSION_OBJ[options.SESSION_ID]?.DS_GLB?.[options.paramsP.dsSessionP];
if (!_ds) {
return;
}
// screen_ready events fire ONCE per screen instance, never again on a re-render/refresh.
// A screen_ready event that triggers a refresh (e.g. an `update` action, or a
// `raise_event` whose handler writes a field) re-renders the screen, which would
// re-fire screen_ready -> refresh -> screen_ready -> ... an infinite render loop (the
// panel "flicker": styled UI paints, then the next cycle wipes it). A refresh reuses
// the same datasource session so this flag persists; navigating to a screen creates a
// new dsSession, so screen_ready fires again there as expected.
if (_ds.screen_ready_fired) {
return;
}
_ds.screen_ready_fired = true;
try {
const viewEventExec_arr = await func.runtime.ui.collect_screen_ready_events({
SESSION_ID: options.SESSION_ID,
paramsP: options.paramsP,
_ds,
});
if (!viewEventExec_arr.length) {
return;
}
for await (const val of viewEventExec_arr) {
let cond = val.eventInfo.data.enabled;
if (val.expression) {
const expCond = await func.expression.get(options.SESSION_ID, val.expression, options.paramsP.dsSessionP, 'condition', options.paramsP.rowIdP);
cond = expCond.result;
}
if (!cond) {
continue;
}
await func.events.execute(
options.SESSION_ID,
null,
val.eventId,
val.triggerId,
val.eventInfo.data.action,
val.eventInfo.data.name,
null,
null,
null,
null,
val.eventInfo.data.action,
null,
options.paramsP.dsSessionP,
val.eventId,
options.sourceP + ' event',
true,
null,
null,
options.paramsP.dsSessionP,
null,
null,
val.eventInfo,
null,
null,
_ds.prog_id,
_ds.nodeId,
_ds.parentDataSourceNo,
options.$div,
);
await func.runtime.ui.refresh_screen_ready_fields({
SESSION_ID: options.SESSION_ID,
paramsP: options.paramsP,
_ds,
$div_objP: options.$div_objP,
});
}
} catch (error) {
console.error('[xuda-runtime] caught xuda_runtime.browser.screenready.events.js:152:', error);
}
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
// Browser-only screen loading lifecycle lives here so screen-ready event flow can stay focused.
func.runtime.ui.screen_loading_done = async function (options) {
let retries = 0;
const interval = setInterval(() => {
const xu_ui_id = func.runtime.ui.get_attr(options.$div, 'xu-ui-id');
if (!func.runtime.ui.find_xu_ui_in_root(options.SESSION_ID, xu_ui_id).length && !func.runtime.ui.find_panel_wrapper_in_root(options.SESSION_ID, xu_ui_id).length && !xu_ui_id && xu_ui_id) {
retries++;
if (retries > 100) {
func.utils.report_issue(options.SESSION_ID, {
code: 'RUN_MSG_RND_050',
source: 'screen_loading_done',
message: 'deadlock detected for screen ready',
type: 'W',
details: {
prog_id: options.paramsP.prog_id,
screen_id: options.paramsP.screenId,
},
});
} else {
return options.$div;
}
}
clearInterval(interval);
func.runtime.ui.execute_screen_ready_events({
SESSION_ID: options.SESSION_ID,
paramsP: options.paramsP,
sourceP: options.paramsP.screenInfo.properties?.renderType,
$div: options.$div,
jobNoP: options.jobNoP,
});
const _session = SESSION_OBJ[options.SESSION_ID];
if (func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
const root_node = func.runtime.ui.get_root_node(options.SESSION_ID);
if (root_node) {
func.runtime.ui.set_attr(root_node, 'data-xuda-client-activation', _session.opt.app_client_activation || 'none');
func.runtime.ui.set_attr(root_node, 'data-xuda-ssr-status', _session.opt.app_client_activation === 'hydrate' ? 'hydrated' : 'taken-over');
}
func.runtime.render.mark_ssr_payload_consumed(options.SESSION_ID);
}
func.events.delete_job(options.SESSION_ID, options.jobNoP);
func.UI.utils.screen_blocker(false, options.paramsP.prog_id + (options.paramsP.sourceScreenP ? '_' + options.paramsP.sourceScreenP : ''));
if (_session.prog_id === options.paramsP.prog_id) {
_session.system_ready = true;
if (_session.engine_mode === 'live_preview' && STUDIO_PEER_CONN_SEND_METHOD) {
STUDIO_PEER_CONN_SEND_METHOD({
service: 'system_ready',
data: {},
id: STUDIO_PEER.id,
source: 'runtime',
session_id: options.SESSION_ID,
app_id: _session.app_id,
gtp_token: _session.gtp_token,
app_token: _session.app_token,
});
}
}
}, 100);
return options.$div;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
// Browser-only generic refresh scan helpers live here so xu-attribute refresh orchestration can stay focused.
// Convert jQuery objects, arrays, NodeLists, or single DOM nodes into a plain array of DOM nodes.
func.runtime.ui._to_node_array = func.runtime.ui._to_node_array || function (input) {
if (!input) {
return [];
}
if (typeof input?.toArray === 'function' && typeof input?.length === 'number' && !input?.nodeType) {
return input.toArray();
}
if (Array.isArray(input)) {
return input;
}
if (input.nodeType) {
return [input];
}
if (typeof input.length === 'number') {
const arr = [];
for (let i = 0; i < input.length; i++) {
if (input[i]) {
arr.push(input[i]);
}
}
return arr;
}
return [];
};
func.runtime.ui.ensure_refresh_dependency_state = function (SESSION_ID) {
const _session = SESSION_OBJ?.[SESSION_ID];
if (!_session) {
return null;
}
if (!_session.refresh_dependency_state) {
_session.refresh_dependency_state = {
dirty: true,
index: {},
elements_by_ui_id: {},
elements_by_nodeid: {},
panel_wrappers_by_id: {},
xu_for_dirty: true,
xu_for_index: {},
panel_wrappers_dirty: true,
panel_wrappers_cache: {},
panel_wrappers_active_cache: {},
runtime_elements_cache: null,
panel_wrapper_elements_cache: null,
};
}
if (typeof _session.refresh_dependency_state.dirty !== 'boolean') {
_session.refresh_dependency_state.dirty = true;
}
if (!_session.refresh_dependency_state.index) {
_session.refresh_dependency_state.index = {};
}
if (!_session.refresh_dependency_state.elements_by_ui_id) {
_session.refresh_dependency_state.elements_by_ui_id = {};
}
if (!_session.refresh_dependency_state.elements_by_nodeid) {
_session.refresh_dependency_state.elements_by_nodeid = {};
}
if (!_session.refresh_dependency_state.panel_wrappers_by_id) {
_session.refresh_dependency_state.panel_wrappers_by_id = {};
}
if (typeof _session.refresh_dependency_state.xu_for_dirty !== 'boolean') {
_session.refresh_dependency_state.xu_for_dirty = true;
}
if (!_session.refresh_dependency_state.xu_for_index) {
_session.refresh_dependency_state.xu_for_index = {};
}
if (typeof _session.refresh_dependency_state.panel_wrappers_dirty !== 'boolean') {
_session.refresh_dependency_state.panel_wrappers_dirty = true;
}
if (!_session.refresh_dependency_state.panel_wrappers_cache) {
_session.refresh_dependency_state.panel_wrappers_cache = {};
}
if (!_session.refresh_dependency_state.panel_wrappers_active_cache) {
_session.refresh_dependency_state.panel_wrappers_active_cache = {};
}
if (typeof _session.refresh_dependency_state.runtime_elements_cache === 'undefined') {
_session.refresh_dependency_state.runtime_elements_cache = null;
}
if (typeof _session.refresh_dependency_state.panel_wrapper_elements_cache === 'undefined') {
_session.refresh_dependency_state.panel_wrapper_elements_cache = null;
}
return _session.refresh_dependency_state;
};
func.runtime.ui.resolve_refresh_index_session_id = function (target, explicit_session_id) {
if (explicit_session_id && SESSION_OBJ?.[explicit_session_id]) {
return explicit_session_id;
}
const target_node = func.runtime.ui.get_first_node(target);
const session_from_data = func.runtime.ui.get_data(target_node)?.xuData?.SESSION_ID;
if (session_from_data) {
return session_from_data;
}
const session_ids = Object.keys(SESSION_OBJ || {});
if (session_ids.length === 1) {
return session_ids[0];
}
return null;
};
func.runtime.ui.mark_refresh_index_dirty = function (SESSION_ID) {
const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
if (!state) {
return false;
}
state.dirty = true;
state.xu_for_dirty = true;
state.panel_wrappers_dirty = true;
state.runtime_elements_cache = null;
state.panel_wrapper_elements_cache = null;
return true;
};
func.runtime.ui.mark_refresh_index_dirty_from_target = function (target, explicit_session_id) {
const session_id = func.runtime.ui.resolve_refresh_index_session_id(target, explicit_session_id);
if (!session_id) {
return false;
}
return func.runtime.ui.mark_refresh_index_dirty(session_id);
};
func.runtime.ui.remove_refresh_dependency_entry = function ($elm, attr_key) {
const session_id = func.runtime.ui.resolve_refresh_index_session_id($elm);
const selector_id = func.runtime.ui.get_attr($elm, 'xu-ui-id');
const elm_data = func.runtime.ui.get_data($elm);
const dependency_store = elm_data?.xuData?.refresh_dependency_by_attr;
if (!session_id || !selector_id || !dependency_store?.[attr_key]) {
return false;
}
const state = func.runtime.ui.ensure_refresh_dependency_state(session_id);
const old_fields = dependency_store[attr_key] || [];
if (!state || state.dirty) {
delete dependency_store[attr_key];
return true;
}
for (const field_id of old_fields) {
const field_index = state.index?.[field_id];
const selector_entry = field_index?.[selector_id];
if (!selector_entry) {
continue;
}
selector_entry.attributes.delete(attr_key);
if (!selector_entry.attributes.size) {
delete field_index[selector_id];
}
if (xu_isEmpty(field_index)) {
delete state.index[field_id];
}
}
delete dependency_store[attr_key];
return true;
};
func.runtime.ui.update_refresh_dependency_entry = function ($elm, attr_key, attr_val) {
const session_id = func.runtime.ui.resolve_refresh_index_session_id($elm);
const selector_id = func.runtime.ui.get_attr($elm, 'xu-ui-id');
const elm_data = func.runtime.ui.get_data($elm);
if (!session_id || !selector_id || !elm_data?.xuData) {
return false;
}
if (!elm_data.xuData.refresh_dependency_by_attr) {
elm_data.xuData.refresh_dependency_by_attr = {};
}
func.runtime.ui.remove_refresh_dependency_entry($elm, attr_key);
const state = func.runtime.ui.ensure_refresh_dependency_state(session_id);
const fields = [...func.runtime.ui.collect_refresh_attribute_fields(elm_data, attr_key, attr_val)];
elm_data.xuData.refresh_dependency_by_attr[attr_key] = fields;
if (!state || state.dirty || !fields.length) {
return !!fields.length;
}
for (const field_id of fields) {
if (!state.index[field_id]) {
state.index[field_id] = {};
}
if (!state.index[field_id][selector_id]) {
state.index[field_id][selector_id] = {
attributes: new Set(),
$elm,
};
}
state.index[field_id][selector_id].attributes.add(attr_key);
state.index[field_id][selector_id].$elm = $elm;
}
return true;
};
func.runtime.ui.prune_stale_refresh_dependencies = function ($elm, node_attributes) {
const elm_data = func.runtime.ui.get_data($elm);
const stored_attributes = elm_data?.xuAttributes;
if (!stored_attributes) {
return false;
}
let changed = false;
for (const key of Object.keys(stored_attributes)) {
if (key.substr(0, 3) !== 'xu-') {
continue;
}
if (Object.prototype.hasOwnProperty.call(node_attributes || {}, key)) {
continue;
}
func.runtime.ui.remove_refresh_dependency_entry($elm, key);
delete stored_attributes[key];
changed = true;
}
return changed;
};
func.runtime.ui.normalize_refresh_field_reference = function (field_ref) {
if (!field_ref) {
return [];
}
const normalized = new Set();
normalized.add(field_ref);
const base_field = field_ref.split(/[.[(]/)[0];
if (base_field) {
normalized.add(base_field);
}
const clean_ref = field_ref.replace(/^\$+/, '');
const data_root_match = clean_ref.match(/^(data|record|row|props|params|parameters|fields|state|ctx|context|this)\.([A-Za-z0-9_$.-]+)/);
if (data_root_match?.[2]) {
normalized.add(data_root_match[2]);
const data_root_base_field = data_root_match[2].split(/[.[(]/)[0];
if (data_root_base_field) {
normalized.add(data_root_base_field);
}
}
return [...normalized].filter(Boolean);
};
func.runtime.ui.extract_expression_refresh_fields = function (elm_data, expression_text, without_var, _visited) {
const fields = new Set();
if (!expression_text) {
return fields;
}
const add_field = function (field_ref) {
for (const normalized of func.runtime.ui.normalize_refresh_field_reference(field_ref)) {
fields.add(normalized);
}
};
const text = typeof expression_text === 'string' ? expression_text : JSON.stringify(expression_text);
if (!text) {
return fields;
}
if (without_var) {
const trimmed = text.trim();
const root_match = trimmed.match(/^([A-Za-z0-9_$.-]+)/);
if (root_match?.[1]) {
add_field(root_match[1]);
}
}
for (const match of text.matchAll(/@([A-Za-z0-9_$.-]+)/g)) {
add_field(match[1]);
}
const data_root_refs = /(^|[^A-Za-z0-9_$])(\$?(?:data|record|row|props|params|parameters|fields|state|ctx|context|this)\.([A-Za-z0-9_$.-]+))/g;
for (const match of text.matchAll(data_root_refs)) {
if (match?.[2]) {
add_field(match[2]);
}
if (match?.[3]) {
add_field(match[3]);
}
}
const bracket_root_refs = /(^|[^A-Za-z0-9_$])(\$?(?:data|record|row|props|params|parameters|fields|state|ctx|context|this))\s*\[\s*['"]([A-Za-z0-9_$.-]+)['"]\s*\]/g;
for (const match of text.matchAll(bracket_root_refs)) {
if (match?.[3]) {
add_field(match[3]);
}
}
const parameters_raw_obj = elm_data?.xuData?.paramsP?.parameters_raw_obj || {};
const parameter_keys = Object.keys(parameters_raw_obj);
const visited = _visited || new Set();
for (let index = 0; index < parameter_keys.length; index++) {
const param_key = parameter_keys[index];
if (visited.has(param_key)) {
continue;
}
const param_val = parameters_raw_obj[param_key];
if (!param_val?.includes?.('@')) {
continue;
}
const param_token = `${without_var ? '' : '@'}${param_key}`;
if (!text.includes(param_token)) {
continue;
}
visited.add(param_key);
const nested_fields = func.runtime.ui.extract_expression_refresh_fields(elm_data, param_val, false, visited);
nested_fields.forEach(function (field_id) {
add_field(field_id);
});
}
return fields;
};
func.runtime.ui.validate_refresh_parameter_reference = function (elm_data, exp, field_id, without_var) {
let expression_text;
if (typeof exp === 'string') {
expression_text = exp;
} else if (typeof exp === 'object') {
expression_text = JSON.stringify(exp);
}
if (!expression_text || !expression_text.includes('@')) {
return false;
}
if (xu_isEmpty(elm_data?.xuData?.paramsP?.parameters_raw_obj)) {
return false;
}
const parameters_raw_obj = elm_data.xuData.paramsP.parameters_raw_obj;
const parameter_keys = Object.keys(parameters_raw_obj);
for (let index = 0; index < parameter_keys.length; index++) {
const param_key = parameter_keys[index];
const param_val = parameters_raw_obj[param_key];
if (!param_val?.includes?.('@')) {
continue;
}
expression_text = expression_text.replaceAll((without_var ? '' : '@') + param_key, param_val);
}
return expression_text.includes((without_var ? '' : '@') + field_id);
};
func.runtime.ui.collect_refresh_attribute_fields = function (elm_data, key, val) {
const fields = new Set();
const add_fields = function (values) {
values?.forEach?.(function (field_id) {
fields.add(field_id);
});
};
if (typeof val !== 'string' && typeof val !== 'object') {
return fields;
}
if (key === 'xu-bind' || key === 'xu-for') {
add_fields(func.runtime.ui.extract_expression_refresh_fields(elm_data, val, true));
add_fields(func.runtime.ui.extract_expression_refresh_fields(elm_data, val, false));
return fields;
}
if (key.substr(0, 6) === 'xu-exp' && key.substr(6, 1) === ':') {
add_fields(func.runtime.ui.extract_expression_refresh_fields(elm_data, val, false));
return fields;
}
if (key.substr(0, 8) === 'xu-class') {
try {
const classes_obj = typeof val === 'string' ? JSON.parse(val) : (val || {});
const class_keys = Object.keys(classes_obj || {});
for (let index = 0; index < class_keys.length; index++) {
add_fields(func.runtime.ui.extract_expression_refresh_fields(elm_data, classes_obj[class_keys[index]], false));
}
} catch (error) {
console.warn('parse error:' + val);
}
return fields;
}
if (key === 'xu-ui-plugin') {
add_fields(func.runtime.ui.extract_expression_refresh_fields(elm_data, JSON.stringify(val), false));
}
return fields;
};
func.runtime.ui.collect_element_refresh_dependencies = function (elm_data) {
const dependency_store = elm_data?.xuData?.refresh_dependency_by_attr;
if (!xu_isEmpty(dependency_store)) {
const stored_dependencies = {};
const attr_keys = Object.keys(dependency_store || {});
for (let attr_index = 0; attr_index < attr_keys.length; attr_index++) {
const attr_key = attr_keys[attr_index];
const fields_arr = dependency_store[attr_key] || [];
for (let field_index = 0; field_index < fields_arr.length; field_index++) {
const field_id = fields_arr[field_index];
if (!stored_dependencies[field_id]) {
stored_dependencies[field_id] = new Set();
}
stored_dependencies[field_id].add(attr_key);
}
}
return stored_dependencies;
}
const dependencies = {};
const xu_attributes = elm_data?.xuAttributes || {};
const attr_keys = Object.keys(xu_attributes);
for (let attr_index = 0; attr_index < attr_keys.length; attr_index++) {
const key = attr_keys[attr_index];
const val = xu_attributes[key];
if (key.substr(0, 3) !== 'xu-') {
continue;
}
const fields = func.runtime.ui.collect_refresh_attribute_fields(elm_data, key, val);
fields.forEach(function (field_id) {
if (!dependencies[field_id]) {
dependencies[field_id] = new Set();
}
dependencies[field_id].add(key);
});
}
return dependencies;
};
func.runtime.ui.matches_refresh_search_root = function (elm, search_roots) {
if (!search_roots?.length) {
return true;
}
const elm_node = func.runtime.ui.get_first_node(elm);
if (!elm_node) {
return false;
}
for (let index = 0; index < search_roots.length; index++) {
const root_node = func.runtime.ui.get_first_node(search_roots[index]);
if (elm_node === root_node || (root_node && root_node.contains(elm_node))) {
return true;
}
}
return false;
};
func.runtime.ui.collect_refresh_attributes = function (elm_data, field_id) {
const attributes = [];
const xu_attributes = elm_data?.xuAttributes || {};
const attr_keys = Object.keys(xu_attributes);
for (let attr_index = 0; attr_index < attr_keys.length; attr_index++) {
const key = attr_keys[attr_index];
const val = xu_attributes[key];
if (typeof val !== 'string' && typeof val !== 'object') {
continue;
}
if (typeof val === 'string' && !val?.includes('@') && key !== 'xu-bind' && key !== 'xu-for') {
continue;
}
if (key.substr(0, 3) !== 'xu-') {
continue;
}
if (key === 'xu-bind' || key === 'xu-for') {
if (val?.includes?.(field_id) || func.runtime.ui.validate_refresh_parameter_reference(elm_data, val, field_id, true)) {
attributes.push(key);
}
continue;
}
if (key.substr(0, 6) === 'xu-exp' && key.substr(6, 1) === ':') {
if (val?.includes?.('@' + field_id) || func.runtime.ui.validate_refresh_parameter_reference(elm_data, val, field_id)) {
attributes.push(key);
}
continue;
}
if (key.substr(0, 8) === 'xu-class') {
try {
const classes_obj = typeof val === 'string' ? JSON.parse(val) : (val || {});
const class_keys = Object.keys(classes_obj || {});
for (let class_index = 0; class_index < class_keys.length; class_index++) {
const cond = classes_obj[class_keys[class_index]];
if (cond?.includes?.('@' + field_id) || func.runtime.ui.validate_refresh_parameter_reference(elm_data, cond, field_id)) {
attributes.push('xu-class');
break;
}
}
if (attributes.length) {
break;
}
} catch (error) {
console.warn('parse error:' + val);
}
continue;
}
if (key === 'xu-ui-plugin') {
const plugin_str = JSON.stringify(val);
if (plugin_str.includes('@' + field_id) || func.runtime.ui.validate_refresh_parameter_reference(elm_data, plugin_str, field_id)) {
attributes.push(key);
break;
}
}
}
return attributes;
};
func.runtime.ui.normalize_refresh_fields = function (fields_arr) {
return new Set((fields_arr || []).map(function (field_id) {
return field_id?.toString?.() || field_id;
}));
};
func.runtime.ui.collect_refresh_attributes_for_fields = function (elm_data, fields_arr, requested_fields) {
const attributes = new Set();
requested_fields = requested_fields || func.runtime.ui.normalize_refresh_fields(fields_arr);
if (!requested_fields.size) {
return [];
}
let dependencies = {};
const dependency_store = elm_data?.xuData?.refresh_dependency_by_attr || {};
const dependency_attr_keys = Object.keys(dependency_store);
for (let attr_index = 0; attr_index < dependency_attr_keys.length; attr_index++) {
const attr_key = dependency_attr_keys[attr_index];
const attr_fields = dependency_store[attr_key] || [];
for (let field_index = 0; field_index < attr_fields.length; field_index++) {
const field_id = attr_fields[field_index];
const normalized_field_id = field_id?.toString?.() || field_id;
if (!requested_fields.has(normalized_field_id)) {
continue;
}
if (!dependencies[normalized_field_id]) {
dependencies[normalized_field_id] = new Set();
}
dependencies[normalized_field_id].add(attr_key);
}
}
if (xu_isEmpty(dependencies)) {
dependencies = func.runtime.ui.collect_element_refresh_dependencies(elm_data);
}
requested_fields.forEach(function (field_id) {
const attr_set = dependencies[field_id];
attr_set?.forEach?.(function (attr_key) {
attributes.add(attr_key);
});
});
return [...attributes];
};
func.runtime.ui.get_refresh_index_node_ids = function (elm_data, $elm) {
const ids = new Set();
const xuData = elm_data?.xuData || {};
const add_id = function (value) {
if (typeof value === 'string' && value) {
ids.add(value);
}
};
add_id(xuData.nodeid);
add_id(xuData.node?.id);
add_id(xuData.node_org?.id);
add_id(func.runtime.ui.get_attr($elm, 'data-xuda-node-id'));
return [...ids];
};
func.runtime.ui.register_refresh_index_element = function (elements_by_ui_id, elements_by_nodeid, panel_wrappers_by_id, selector_id, elm_data, $elm) {
if (!selector_id || !elm_data?.xuData) {
return false;
}
elements_by_ui_id[selector_id] = $elm;
const panel_wrapper_id = func.runtime.ui.get_attr($elm, 'xu-panel-wrapper-id');
if (panel_wrapper_id) {
panel_wrappers_by_id[panel_wrapper_id] = $elm;
}
const node_ids = func.runtime.ui.get_refresh_index_node_ids(elm_data, $elm);
if (!node_ids.length) {
return true;
}
for (let node_index = 0; node_index < node_ids.length; node_index++) {
const node_id = node_ids[node_index];
if (!elements_by_nodeid[node_id]) {
elements_by_nodeid[node_id] = {};
}
elements_by_nodeid[node_id][selector_id] = $elm;
}
return true;
};
func.runtime.ui.find_refresh_elements_by_attr = function (root_nodes_input, attr_name, attr_value, first_only = false) {
const root_nodes = func.runtime.ui._to_node_array(root_nodes_input);
if (!root_nodes.length || !attr_name) {
return func.runtime.ui._wrap_matches([]);
}
const selector = typeof attr_value === 'undefined' ? `[${attr_name}]` : `[${attr_name}="${attr_value}"]`;
const elements = [];
for (let root_index = 0; root_index < root_nodes.length; root_index++) {
const root_node = root_nodes[root_index];
if (root_node?.matches?.(selector)) {
elements.push(root_node);
if (first_only) {
break;
}
}
if (first_only && elements.length) {
break;
}
if (first_only) {
const first_match = root_node?.querySelector?.(selector);
if (first_match) {
elements.push(first_match);
break;
}
continue;
}
const descendants = root_node?.querySelectorAll?.(selector) || [];
for (let index = 0; index < descendants.length; index++) {
elements.push(descendants[index]);
}
}
return func.runtime.ui._wrap_matches(elements);
};
func.runtime.ui.get_preferred_live_element = function (target) {
const elements = func.runtime.ui._to_node_array(target);
if (!elements.length) {
return null;
}
let best_node = null;
let best_score = -Infinity;
for (let index = 0; index < elements.length; index++) {
const node = elements[index];
if (!node) {
continue;
}
const node_data = func.runtime.ui.get_data(node);
let score = 0;
if (node.isConnected) {
score += 100;
}
if (!node_data?.xuData?.pending_to_delete) {
score += 50;
}
if (node.getClientRects?.().length) {
score += 25;
}
if (!node.hidden) {
score += 10;
}
if (score >= best_score) {
best_score = score;
best_node = node;
}
}
return best_node || elements[elements.length - 1] || null;
};
func.runtime.ui.get_refresh_indexed_element_by_ui_id = function (SESSION_ID, selector_id) {
const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
if (!state) {
return null;
}
if (state.dirty) {
func.runtime.ui.rebuild_refresh_dependency_index(SESSION_ID);
}
let elm = state.elements_by_ui_id?.[selector_id];
elm = func.runtime.ui.get_preferred_live_element(elm);
if (elm?.isConnected) {
state.elements_by_ui_id[selector_id] = elm;
return elm;
}
const root_nodes = func.runtime.ui.get_refresh_index_root(SESSION_ID);
elm = func.runtime.ui.get_preferred_live_element(func.runtime.ui.find_refresh_elements_by_attr(root_nodes, 'xu-ui-id', selector_id));
if (elm) {
state.elements_by_ui_id[selector_id] = elm;
state.runtime_elements_cache = null;
} else if (state.elements_by_ui_id?.[selector_id]) {
delete state.elements_by_ui_id[selector_id];
state.runtime_elements_cache = null;
}
return elm;
};
func.runtime.ui.get_refresh_indexed_elements_by_node_id = function (SESSION_ID, node_id) {
const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
if (!state) {
return func.runtime.ui._wrap_matches([]);
}
if (state.dirty) {
func.runtime.ui.rebuild_refresh_dependency_index(SESSION_ID);
}
const indexed_elements = state.elements_by_nodeid?.[node_id] || {};
const elements = [];
const selector_ids = Object.keys(indexed_elements);
for (let index = 0; index < selector_ids.length; index++) {
const selector_id = selector_ids[index];
let candidate = indexed_elements[selector_id];
if (!candidate?.isConnected) {
candidate = func.runtime.ui.get_refresh_indexed_element_by_ui_id(SESSION_ID, selector_id);
if (!candidate) {
delete indexed_elements[selector_id];
continue;
}
indexed_elements[selector_id] = candidate;
}
if (candidate) {
elements.push(candidate);
}
}
return func.runtime.ui._wrap_matches(elements);
};
func.runtime.ui.get_refresh_index_root = function (SESSION_ID) {
const root = func.runtime.ui.get_first_node(func.runtime.ui.get_root_element?.(SESSION_ID));
if (root) {
return [root];
}
return [document.body];
};
func.runtime.ui.get_refresh_index_elements = function (SESSION_ID, search_root_input) {
const root_nodes = func.runtime.ui._to_node_array(search_root_input);
if (!root_nodes.length) {
const default_roots = func.runtime.ui.get_refresh_index_root(SESSION_ID);
root_nodes.push(...default_roots);
}
const elements = [];
for (let root_index = 0; root_index < root_nodes.length; root_index++) {
const root_node = root_nodes[root_index];
if (root_node?.matches?.('[xu-ui-id]')) {
elements.push(root_node);
}
const descendants = root_node?.querySelectorAll?.('[xu-ui-id]') || [];
for (let element_index = 0; element_index < descendants.length; element_index++) {
elements.push(descendants[element_index]);
}
}
return func.runtime.ui._wrap_matches(elements);
};
func.runtime.ui.get_refresh_indexed_runtime_elements = function (SESSION_ID) {
const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
if (!state) {
return [];
}
if (state.dirty) {
func.runtime.ui.rebuild_refresh_dependency_index(SESSION_ID);
}
if (state.runtime_elements_cache !== null) {
return state.runtime_elements_cache;
}
const elements = [];
const selector_ids = Object.keys(state.elements_by_ui_id || {});
for (let index = 0; index < selector_ids.length; index++) {
const selector_id = selector_ids[index];
let candidate = state.elements_by_ui_id[selector_id];
if (!candidate?.isConnected) {
candidate = func.runtime.ui.get_refresh_indexed_element_by_ui_id(SESSION_ID, selector_id);
}
if (candidate) {
elements.push(candidate);
}
}
state.runtime_elements_cache = elements;
return state.runtime_elements_cache;
};
func.runtime.ui.get_refresh_indexed_panel_wrapper_by_id = function (SESSION_ID, wrapper_id) {
const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
if (!state) {
return null;
}
if (state.dirty) {
func.runtime.ui.rebuild_refresh_dependency_index(SESSION_ID);
}
let elm = state.panel_wrappers_by_id?.[wrapper_id];
elm = func.runtime.ui.get_preferred_live_element(elm);
if (elm?.isConnected) {
state.panel_wrappers_by_id[wrapper_id] = elm;
return elm;
}
const root_nodes = func.runtime.ui.get_refresh_index_root(SESSION_ID);
elm = func.runtime.ui.get_preferred_live_element(func.runtime.ui.find_refresh_elements_by_attr(root_nodes, 'xu-panel-wrapper-id', wrapper_id));
if (elm) {
state.panel_wrappers_by_id[wrapper_id] = elm;
state.panel_wrapper_elements_cache = null;
} else if (state.panel_wrappers_by_id?.[wrapper_id]) {
delete state.panel_wrappers_by_id[wrapper_id];
state.panel_wrapper_elements_cache = null;
}
return elm;
};
func.runtime.ui.get_refresh_indexed_panel_wrappers = function (SESSION_ID) {
const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
if (!state) {
return [];
}
if (state.dirty) {
func.runtime.ui.rebuild_refresh_dependency_index(SESSION_ID);
}
if (state.panel_wrapper_elements_cache !== null) {
return state.panel_wrapper_elements_cache;
}
const elements = [];
const wrapper_ids = Object.keys(state.panel_wrappers_by_id || {});
for (let index = 0; index < wrapper_ids.length; index++) {
const wrapper_id = wrapper_ids[index];
let candidate = state.panel_wrappers_by_id[wrapper_id];
if (!candidate?.isConnected) {
candidate = func.runtime.ui.get_refresh_indexed_panel_wrapper_by_id(SESSION_ID, wrapper_id);
}
if (candidate) {
elements.push(candidate);
}
}
state.panel_wrapper_elements_cache = elements;
return state.panel_wrapper_elements_cache;
};
func.runtime.ui.rebuild_refresh_dependency_index = function (SESSION_ID) {
const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
if (!state) {
return {};
}
const index = {};
const elements_by_ui_id = {};
const elements_by_nodeid = {};
const panel_wrappers_by_id = {};
const refresh_elements = func.runtime.ui.get_refresh_index_elements(SESSION_ID);
for (let element_index = 0; element_index < refresh_elements.length; element_index++) {
const elm = refresh_elements[element_index];
const elm_data = func.runtime.ui.get_data(elm);
const selector_id = func.runtime.ui.get_attr(elm, 'xu-ui-id');
if (!selector_id || !elm_data?.xuData || !elm_data?.xuAttributes) {
continue;
}
func.runtime.ui.register_refresh_index_element(elements_by_ui_id, elements_by_nodeid, panel_wrappers_by_id, selector_id, elm_data, elm);
const dependencies = func.runtime.ui.collect_element_refresh_dependencies(elm_data);
const field_ids = Object.keys(dependencies);
for (let field_index = 0; field_index < field_ids.length; field_index++) {
const field_id = field_ids[field_index];
const attrs = dependencies[field_id];
if (!index[field_id]) {
index[field_id] = {};
}
if (!index[field_id][selector_id]) {
index[field_id][selector_id] = {
attributes: new Set(),
$elm: elm,
};
}
for (const attr_key of attrs || []) {
index[field_id][selector_id].attributes.add(attr_key);
}
}
}
state.index = index;
state.elements_by_ui_id = elements_by_ui_id;
state.elements_by_nodeid = elements_by_nodeid;
state.panel_wrappers_by_id = panel_wrappers_by_id;
const runtime_elements = [];
const runtime_selector_ids = Object.keys(elements_by_ui_id);
for (let index = 0; index < runtime_selector_ids.length; index++) {
const element = elements_by_ui_id[runtime_selector_ids[index]];
if (element) {
runtime_elements.push(element);
}
}
const panel_wrapper_elements = [];
const panel_wrapper_ids = Object.keys(panel_wrappers_by_id);
for (let index = 0; index < panel_wrapper_ids.length; index++) {
const element = panel_wrappers_by_id[panel_wrapper_ids[index]];
if (element) {
panel_wrapper_elements.push(element);
}
}
state.runtime_elements_cache = runtime_elements;
state.panel_wrapper_elements_cache = panel_wrapper_elements;
state.dirty = false;
return index;
};
func.runtime.ui.collect_refresh_selectors_from_index = function (options) {
const perf_end = func.runtime?.perf?.start?.(options.SESSION_ID, 'collect_refresh_selectors_from_index');
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_selector_index_calls');
const state = func.runtime.ui.ensure_refresh_dependency_state(options.SESSION_ID);
if (!state) {
perf_end?.();
return {};
}
if (state.dirty) {
func.runtime.ui.rebuild_refresh_dependency_index(options.SESSION_ID);
}
const selectors = {};
const index = state.index || {};
const search_roots = func.runtime.ui._to_node_array(options.$elm_to_search);
const search_root = search_roots.length ? search_roots : null;
const requested_fields = [...func.runtime.ui.normalize_refresh_fields(options.fields_arr)];
for (let field_index = 0; field_index < requested_fields.length; field_index++) {
const val_field = requested_fields[field_index];
const indexed_elements = index[val_field] || {};
const selector_ids = Object.keys(indexed_elements);
for (let selector_index = 0; selector_index < selector_ids.length; selector_index++) {
const selector_id = selector_ids[selector_index];
const entry = indexed_elements[selector_id];
let elm = entry.$elm;
if (!elm?.isConnected) {
elm = func.runtime.ui.get_refresh_indexed_element_by_ui_id(options.SESSION_ID, selector_id);
entry.$elm = elm;
}
if (!elm) {
delete indexed_elements[selector_id];
continue;
}
const elm_data = func.runtime.ui.get_data(elm);
if (!elm_data?.xuData) {
delete indexed_elements[selector_id];
continue;
}
if (!func.runtime.ui.matches_refresh_search_root(elm, search_root)) {
continue;
}
if (typeof options.dsSession_changed !== 'undefined' && elm_data.xuData.paramsP && elm_data.xuData.paramsP.dsSessionP < options.dsSession_changed) {
continue;
}
if (!selectors[selector_id]) {
selectors[selector_id] = { attributes: new Set(), $elm: elm };
}
const attr_values = [...entry.attributes];
for (let attr_index = 0; attr_index < attr_values.length; attr_index++) {
selectors[selector_id].attributes.add(attr_values[attr_index]);
}
}
}
const selector_result_ids = Object.keys(selectors);
for (let selector_index = 0; selector_index < selector_result_ids.length; selector_index++) {
const selector_id = selector_result_ids[selector_index];
selectors[selector_id].attributes = [...selectors[selector_id].attributes];
}
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_selector_index_found', selector_result_ids.length);
perf_end?.();
return selectors;
};
func.runtime.ui.collect_refresh_selectors_by_scan = function (options) {
const perf_end = func.runtime?.perf?.start?.(options.SESSION_ID, 'collect_refresh_selectors_by_scan');
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_selector_scan_calls');
const selectors = {};
const search_roots = func.runtime.ui._to_node_array(options.$elm_to_search);
const search_root = search_roots.length ? search_roots : func.runtime.ui.get_refresh_index_root(options.SESSION_ID);
const requested_fields = func.runtime.ui.normalize_refresh_fields(options.fields_arr);
const refresh_elements = func.runtime.ui.get_refresh_index_elements(options.SESSION_ID, search_root);
for (let element_index = 0; element_index < refresh_elements.length; element_index++) {
const elm = refresh_elements[element_index];
const elm_data = func.runtime.ui.get_data(elm);
if (!elm_data.xuData) {
continue;
}
if (typeof options.dsSession_changed !== 'undefined' && elm_data.xuData.paramsP && elm_data.xuData.paramsP.dsSessionP < options.dsSession_changed) {
continue;
}
const attributes = func.runtime.ui.collect_refresh_attributes_for_fields(elm_data, options.fields_arr, requested_fields);
const selector_id = func.runtime.ui.get_attr(elm, 'xu-ui-id');
if (!attributes.length || !selector_id) {
continue;
}
if (!selectors[selector_id]) {
selectors[selector_id] = { attributes: new Set(), $elm: elm };
}
for (let attr_index = 0; attr_index < attributes.length; attr_index++) {
selectors[selector_id].attributes.add(attributes[attr_index]);
}
}
const selector_ids = Object.keys(selectors);
for (let selector_index = 0; selector_index < selector_ids.length; selector_index++) {
const selector_id = selector_ids[selector_index];
selectors[selector_id].attributes = [...selectors[selector_id].attributes];
}
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_selector_scan_found', selector_ids.length);
perf_end?.();
return selectors;
};
func.runtime.ui.merge_refresh_selector_sets = function (target, source) {
if (xu_isEmpty(source)) {
return target || {};
}
target = target || {};
const selector_ids = Object.keys(source || {});
for (let selector_index = 0; selector_index < selector_ids.length; selector_index++) {
const selector_id = selector_ids[selector_index];
const source_entry = source[selector_id];
if (!source_entry) {
continue;
}
if (!target[selector_id]) {
target[selector_id] = {
attributes: [],
$elm: source_entry.$elm,
};
}
if (!target[selector_id].$elm && source_entry.$elm) {
target[selector_id].$elm = source_entry.$elm;
}
const attributes = new Set(target[selector_id].attributes || []);
for (const attr of source_entry.attributes || []) {
attributes.add(attr);
}
target[selector_id].attributes = [...attributes];
}
return target;
};
func.runtime.ui.prog_ui_xu_render_dependency_cache = func.runtime.ui.prog_ui_xu_render_dependency_cache || new WeakMap();
func.runtime.ui.get_prog_ui_node_attribute_sources = function (item) {
const sources = [];
if (!xu_isEmpty(item?.attributes)) {
sources.push(item.attributes);
}
if (!xu_isEmpty(item?.attributes_raw_obj)) {
sources.push(item.attributes_raw_obj);
}
if (!xu_isEmpty(item?.attributes_raw)) {
sources.push(item.attributes_raw);
}
return sources;
};
func.runtime.ui.get_prog_ui_node_attribute_value = function (item, attr) {
const sources = func.runtime.ui.get_prog_ui_node_attribute_sources(item);
for (let source_index = 0; source_index < sources.length; source_index++) {
const source = sources[source_index];
if (typeof source?.[attr] !== 'undefined') {
return source[attr];
}
}
return undefined;
};
func.runtime.ui.get_prog_ui_node_xu_render_attr = function (item) {
const sources = func.runtime.ui.get_prog_ui_node_attribute_sources(item);
for (let source_index = 0; source_index < sources.length; source_index++) {
const source = sources[source_index];
if (typeof source?.['xu-exp:xu-render'] !== 'undefined') {
return 'xu-exp:xu-render';
}
if (typeof source?.['xu-render'] !== 'undefined') {
return 'xu-render';
}
}
return null;
};
func.runtime.ui.get_live_render_gate_elements_for_entry = function (options, entry) {
if (!entry?.render_gate_parent_node_id) {
return [];
}
const gate_elements = func.runtime.ui.get_refresh_indexed_elements_by_node_id(options.SESSION_ID, entry.render_gate_parent_node_id).toArray();
const panel_wrapper = entry.panel_wrapper_ui_id
? func.runtime.ui.get_refresh_indexed_element_by_ui_id(options.SESSION_ID, entry.panel_wrapper_ui_id)
: null;
const panel_wrapper_node = func.runtime.ui.get_first_node(panel_wrapper);
const search_roots = func.runtime.ui._to_node_array(options.$elm_to_search);
const search_root = search_roots.length ? search_roots : null;
return gate_elements.filter(function (gate_element) {
const gate_data = func.runtime.ui.get_data(gate_element);
if (!gate_element?.isConnected || !gate_data?.xuData || gate_data.xuData.pending_to_delete) {
return false;
}
if (panel_wrapper_node && gate_element !== panel_wrapper_node && !panel_wrapper_node.contains(gate_element)) {
return false;
}
if (entry.panel_ds_session != null) {
const gate_ds_session = gate_data?.xuData?.paramsP?.dsSessionP;
if (gate_ds_session != null && gate_ds_session !== entry.panel_ds_session) {
return false;
}
}
return func.runtime.ui.matches_refresh_search_root(gate_element, search_root);
});
};
func.runtime.ui.collect_prog_ui_xu_render_dependencies = function (progUi, index = {}, parent_node = null, render_gate_parent_node = null) {
if (!Array.isArray(progUi)) {
return index;
}
for (let item_index = 0; item_index < progUi.length; item_index++) {
const item = progUi[item_index];
const attribute_sources = func.runtime.ui.get_prog_ui_node_attribute_sources(item);
for (let source_index = 0; source_index < attribute_sources.length; source_index++) {
const attributes = attribute_sources[source_index];
const attribute_keys = Object.keys(attributes || {});
for (let attr_index = 0; attr_index < attribute_keys.length; attr_index++) {
const attr = attribute_keys[attr_index];
if (attr !== 'xu-exp:xu-render' && attr !== 'xu-render') {
continue;
}
const val = attributes[attr];
const fields = new Set();
func.runtime.ui.extract_expression_refresh_fields({}, val, true).forEach(function (field_id) {
fields.add(field_id);
});
func.runtime.ui.extract_expression_refresh_fields({}, val, false).forEach(function (field_id) {
fields.add(field_id);
});
for (const field_id of fields) {
if (!index[field_id]) {
index[field_id] = {};
}
index[field_id][`${item.id || ''}::${attr}`] = {
node_id: item.id,
attr,
attr_value: val,
key: item_index,
node: item,
parent_node,
parent_node_id: parent_node?.id || null,
render_gate_parent_node,
render_gate_parent_node_id: render_gate_parent_node?.id || null,
};
}
}
}
if (item?.children) {
const next_render_gate_parent_node = func.runtime.ui.get_prog_ui_node_xu_render_attr(item) ? item : render_gate_parent_node;
func.runtime.ui.collect_prog_ui_xu_render_dependencies(item.children, index, item, next_render_gate_parent_node);
}
}
return index;
};
func.runtime.ui.get_prog_ui_xu_render_dependencies = function (progUi) {
if (!Array.isArray(progUi)) {
return {};
}
let cached_dependencies = func.runtime.ui.prog_ui_xu_render_dependency_cache.get(progUi);
if (cached_dependencies) {
return cached_dependencies;
}
cached_dependencies = func.runtime.ui.collect_prog_ui_xu_render_dependencies(progUi, {});
func.runtime.ui.prog_ui_xu_render_dependency_cache.set(progUi, cached_dependencies);
return cached_dependencies;
};
func.runtime.ui.merge_prog_ui_xu_render_dependencies = function (target_index, source_index, context = null) {
if (xu_isEmpty(source_index)) {
return target_index || {};
}
target_index = target_index || {};
const field_ids = Object.keys(source_index);
for (let field_index = 0; field_index < field_ids.length; field_index++) {
const field_id = field_ids[field_index];
const entry_obj = source_index[field_id];
if (!target_index[field_id]) {
target_index[field_id] = {};
}
const entry_keys = Object.keys(entry_obj || {});
for (let entry_index = 0; entry_index < entry_keys.length; entry_index++) {
const entry_key = entry_keys[entry_index];
const entry = entry_obj[entry_key];
const contextual_entry = context
? {
...entry,
panel_wrapper_ui_id: context.panel_wrapper_ui_id || null,
panel_ds_session: context.panel_ds_session || null,
panel_prog_id: context.panel_prog_id || null,
}
: entry;
const contextual_entry_key = context?.panel_wrapper_ui_id ? `${context.panel_wrapper_ui_id}::${entry_key}` : entry_key;
contextual_entry.dependency_key = contextual_entry_key;
target_index[field_id][contextual_entry_key] = contextual_entry;
}
}
return target_index;
};
func.runtime.ui.get_cached_refresh_panel_wrappers = function (SESSION_ID, include_disabled = true) {
const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
if (!state || state.panel_wrappers_dirty) {
return {};
}
return include_disabled ? state.panel_wrappers_cache || {} : state.panel_wrappers_active_cache || {};
};
func.runtime.ui.should_debug_xu_render_field = function (options, field_id) {
return (options?.fields_arr || []).includes(field_id);
};
func.runtime.ui.debug_xu_render_field = function (options, label, payload) {
if (!func.runtime.ui.should_debug_xu_render_field(options, 'open_modal_v')) {
return;
}
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] xu_render_open_modal_probe ' +
JSON.stringify({
label,
fields: options?.fields_arr || [],
...payload,
}),
);
} catch (e) {}
};
func.runtime.ui.collect_all_prog_ui_xu_render_dependencies = function (options, panels_obj) {
const index = {};
const $embed_container = func.runtime.ui.get_embed_screen_containers();
const root_prog_ui = func.runtime.ui.get_data($embed_container)?.xuData?.screenInfo?.progUi;
if (root_prog_ui) {
func.runtime.ui.merge_prog_ui_xu_render_dependencies(index, func.runtime.ui.get_prog_ui_xu_render_dependencies(root_prog_ui));
}
const resolved_panels_obj = panels_obj || func.runtime.ui.get_cached_refresh_panel_wrappers(options.SESSION_ID, true);
const panel_ids = Object.keys(resolved_panels_obj || {});
func.runtime.ui.debug_xu_render_field(options, 'collect_all_start', {
root_prog_ui: !!root_prog_ui,
panel_count: panel_ids.length,
panels: panel_ids.slice(0, 20).map(function (panel_id) {
const panel_val = resolved_panels_obj[panel_id];
return {
panel_id,
prog_id: panel_val?._ds?.prog_id || null,
dsSession: panel_val?._ds?.dsSession || null,
progUi: Array.isArray(panel_val?.progUi) ? panel_val.progUi.length : null,
};
}),
});
for (let panel_index = 0; panel_index < panel_ids.length; panel_index++) {
const panel_val = resolved_panels_obj[panel_ids[panel_index]];
if (!panel_val?.progUi) {
continue;
}
func.runtime.ui.merge_prog_ui_xu_render_dependencies(index, func.runtime.ui.get_prog_ui_xu_render_dependencies(panel_val.progUi), {
panel_wrapper_ui_id: panel_ids[panel_index],
panel_ds_session: panel_val?._ds?.dsSession || null,
panel_prog_id: panel_val?._ds?.prog_id || null,
});
}
func.runtime.ui.collect_live_node_org_xu_render_dependencies(options, index);
func.runtime.ui.debug_xu_render_field(options, 'collect_all_done', {
open_modal_keys: Object.keys(index.open_modal_v || {}),
dependency_fields: Object.keys(index).filter(function (field_id) {
return field_id === 'open_modal_v' || field_id.includes('modal');
}),
});
return index;
};
func.runtime.ui.collect_live_node_org_xu_render_dependencies = function (options, index = {}) {
if (!options?.SESSION_ID) {
return index;
}
const $root = func.UI?.worker?.get_session_root ? func.UI.worker.get_session_root(options.SESSION_ID) : func.runtime.ui.get_embed_screen_containers();
const live_elements = func.runtime.ui.get_refresh_index_elements
? func.runtime.ui.get_refresh_index_elements(options.SESSION_ID, $root).toArray()
: Array.from(func.runtime.ui.get_first_node($root)?.querySelectorAll?.('[xu-ui-id]') || []);
const seen_parent_nodes = new Set();
for (let live_index = 0; live_index < live_elements.length; live_index++) {
const live_element = live_elements[live_index];
const live_xu_data = func.runtime.ui.get_data(live_element)?.xuData;
const parent_ui_id = func.runtime.ui.get_attr(live_element, 'xu-ui-id');
const parent_node = live_xu_data?.node_org || live_xu_data?.node;
if (!parent_ui_id || !parent_node?.id || !Array.isArray(parent_node.children) || !parent_node.children.length) {
continue;
}
const seen_key = `${parent_ui_id}::${parent_node.id}`;
if (seen_parent_nodes.has(seen_key)) {
continue;
}
seen_parent_nodes.add(seen_key);
const local_index = {};
func.runtime.ui.collect_prog_ui_xu_render_dependencies(parent_node.children, local_index, parent_node);
const field_ids = Object.keys(local_index);
for (let field_index = 0; field_index < field_ids.length; field_index++) {
const field_id = field_ids[field_index];
const entry_obj = local_index[field_id] || {};
if (!index[field_id]) {
index[field_id] = {};
}
const entry_keys = Object.keys(entry_obj);
for (let entry_index = 0; entry_index < entry_keys.length; entry_index++) {
const entry_key = entry_keys[entry_index];
const entry = entry_obj[entry_key];
if (entry?.parent_node_id !== parent_node.id) {
continue;
}
const live_entry_key = `${parent_ui_id}::${entry_key}`;
index[field_id][live_entry_key] = {
...entry,
dependency_key: live_entry_key,
parent_element_ui_id: parent_ui_id,
parent_ds_session: live_xu_data?.paramsP?.dsSessionP || null,
parent_prog_id: live_xu_data?.paramsP?.prog_id || null,
};
}
}
}
return index;
};
func.runtime.ui.collect_refresh_xu_render_selectors_from_prog_ui = function (options) {
const selectors = {};
const dependencies = func.runtime.ui.collect_all_prog_ui_xu_render_dependencies(options);
const requested_fields = [...func.runtime.ui.normalize_refresh_fields(options.fields_arr)];
const search_roots = func.runtime.ui._to_node_array(options.$elm_to_search);
const search_root = search_roots.length ? search_roots : null;
for (let field_index = 0; field_index < requested_fields.length; field_index++) {
const field_id = requested_fields[field_index];
const field_entries = dependencies?.[field_id] || {};
const entry_keys = Object.keys(field_entries);
for (let entry_index = 0; entry_index < entry_keys.length; entry_index++) {
const entry = field_entries[entry_keys[entry_index]];
if (!entry?.node_id) {
continue;
}
const $elements = func.runtime.ui.get_refresh_indexed_elements_by_node_id(options.SESSION_ID, entry.node_id);
const element_nodes = func.runtime.ui._to_node_array($elements);
for (let element_index = 0; element_index < element_nodes.length; element_index++) {
const elm = element_nodes[element_index];
const elm_data = func.runtime.ui.get_data(elm);
const selector_id = func.runtime.ui.get_attr(elm, 'xu-ui-id');
if (!selector_id || !elm_data?.xuData || elm_data.xuData.pending_to_delete) {
continue;
}
if (!func.runtime.ui.matches_refresh_search_root(elm, search_root)) {
continue;
}
if (!selectors[selector_id]) {
selectors[selector_id] = { attributes: new Set(), $elm: elm };
}
selectors[selector_id].attributes.add(entry.attr);
}
}
}
const selector_ids = Object.keys(selectors);
for (let selector_index = 0; selector_index < selector_ids.length; selector_index++) {
const selector_id = selector_ids[selector_index];
selectors[selector_id].attributes = [...selectors[selector_id].attributes];
}
if (!xu_isEmpty(selectors)) {
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] refresh_selector_prog_ui_xu_render ' +
JSON.stringify({
fields: options.fields_arr || [],
selectors: selector_ids,
}),
);
} catch (e) {}
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_selector_prog_ui_xu_render_used');
}
return selectors;
};
func.runtime.ui.collect_prog_ui_xu_render_entries_for_fields = function (options, panels_obj) {
const dependencies = func.runtime.ui.collect_all_prog_ui_xu_render_dependencies(options, panels_obj);
const requested_fields = [...func.runtime.ui.normalize_refresh_fields(options.fields_arr)];
const entries = {};
for (let field_index = 0; field_index < requested_fields.length; field_index++) {
const field_id = requested_fields[field_index];
const field_entries = dependencies?.[field_id] || {};
const entry_keys = Object.keys(field_entries);
for (let entry_index = 0; entry_index < entry_keys.length; entry_index++) {
const entry_key = entry_keys[entry_index];
const entry = field_entries[entry_key];
if (!entry?.node_id || !entry?.node) {
continue;
}
entries[entry.dependency_key || `${entry.panel_wrapper_ui_id || ''}::${entry.parent_element_ui_id || ''}::${entry.node_id}::${entry.attr}`] = entry;
}
}
func.runtime.ui.debug_xu_render_field(options, 'entries_for_fields', {
count: Object.keys(entries).length,
entries: Object.values(entries)
.slice(0, 20)
.map(function (entry) {
return {
node_id: entry?.node_id || null,
attr: entry?.attr || null,
parent_node_id: entry?.parent_node_id || null,
render_gate_parent_node_id: entry?.render_gate_parent_node_id || null,
parent_element_ui_id: entry?.parent_element_ui_id || null,
panel_wrapper_ui_id: entry?.panel_wrapper_ui_id || null,
panel_prog_id: entry?.panel_prog_id || null,
parent_prog_id: entry?.parent_prog_id || null,
attr_value: entry?.attr_value || null,
};
}),
});
return Object.values(entries);
};
func.runtime.ui.is_prog_ui_xu_render_overlay_node = function (entry) {
const root_node = entry?.node;
if (!root_node) {
return false;
}
const stack = [root_node];
let inspected = 0;
while (stack.length && inspected < 40) {
const node = stack.shift();
inspected++;
const attrs = {};
const sources = func.runtime.ui.get_prog_ui_node_attribute_sources(node);
for (let source_index = 0; source_index < sources.length; source_index++) {
Object.assign(attrs, sources[source_index] || {});
}
const class_value = `${attrs.class || attrs['xu-class'] || attrs['xu-exp:class'] || ''}`;
const style_value = `${attrs.style || attrs['xu-style'] || attrs['xu-exp:style'] || ''}`;
if (
attrs.role === 'dialog' ||
attrs['aria-modal'] === true ||
attrs['aria-modal'] === 'true' ||
/\bfixed\b/.test(class_value) ||
/position\s*:\s*fixed/i.test(style_value)
) {
return true;
}
const children = Array.isArray(node?.children) ? node.children : [];
for (let child_index = 0; child_index < children.length; child_index++) {
stack.push(children[child_index]);
}
}
return false;
};
func.runtime.ui.find_live_xu_render_parent_by_node_data = function (options, entry, root_node) {
if (!entry?.parent_node_id || !root_node) {
return [];
}
const candidates = [];
if (root_node.matches?.('[xu-ui-id]')) {
candidates.push(root_node);
}
const descendants = root_node.querySelectorAll?.('[xu-ui-id]') || [];
for (let index = 0; index < descendants.length; index++) {
candidates.push(descendants[index]);
}
return candidates.filter(function (candidate) {
const candidate_data = func.runtime.ui.get_data(candidate);
const ids = func.runtime.ui.get_refresh_index_node_ids(candidate_data, candidate);
if (!ids.includes(entry.parent_node_id)) {
return false;
}
if (entry.panel_ds_session != null) {
const ds_session = candidate_data?.xuData?.paramsP?.dsSessionP;
if (ds_session != null && ds_session !== entry.panel_ds_session) {
return false;
}
}
return candidate?.isConnected && candidate_data?.xuData;
});
};
func.runtime.ui.should_mount_xu_render_entry_at_screen_root = function (entry) {
return (
func.runtime.ui.is_prog_ui_xu_render_overlay_node(entry) &&
(entry?._used_parent_fallback === 'panel_session_element' ||
entry?._used_parent_fallback === 'panel_virtual_parent' ||
entry?._used_parent_fallback === 'embed_screen')
);
};
func.runtime.ui.get_xu_render_screen_root_mount = function () {
const roots = func.runtime.ui._to_node_array(func.runtime.ui.get_embed_screen_containers());
for (let root_index = 0; root_index < roots.length; root_index++) {
const root = roots[root_index];
if (root?.isConnected) {
return root;
}
}
return roots[0] || null;
};
func.runtime.ui.get_prog_ui_xu_render_parent_elements = function (options, entry) {
let parent_elements = [];
if (entry.parent_element_ui_id) {
const parent_element = func.runtime.ui.get_refresh_indexed_element_by_ui_id(options.SESSION_ID, entry.parent_element_ui_id);
parent_elements = func.runtime.ui._to_node_array(parent_element);
} else if (entry.parent_node_id) {
parent_elements = func.runtime.ui.get_refresh_indexed_elements_by_node_id(options.SESSION_ID, entry.parent_node_id).toArray();
if (entry.panel_wrapper_ui_id) {
const panel_wrapper = func.runtime.ui.get_refresh_indexed_element_by_ui_id(options.SESSION_ID, entry.panel_wrapper_ui_id);
const panel_wrapper_node = func.runtime.ui.get_first_node(panel_wrapper);
if (panel_wrapper_node) {
parent_elements = parent_elements.filter(function (parent_element) {
return parent_element === panel_wrapper_node || panel_wrapper_node.contains(parent_element);
});
}
}
} else if (entry.panel_wrapper_ui_id) {
const panel_wrapper = func.runtime.ui.get_refresh_indexed_element_by_ui_id(options.SESSION_ID, entry.panel_wrapper_ui_id);
parent_elements = func.runtime.ui._to_node_array(panel_wrapper);
} else {
parent_elements = func.runtime.ui._to_node_array(func.runtime.ui.get_embed_screen_containers());
}
// Fallback: the declared parent (parent_element_ui_id / parent_node_id) may
// itself be an unrendered conditional node, so the lookup above yields zero
// live mount points and the missing-render (e.g. an open-modal xu-render)
// silently bails. Recover a mount point so the node can render.
let used_parent_fallback = null;
if (!parent_elements.length && (entry.parent_element_ui_id || entry.parent_node_id)) {
if (entry.render_gate_parent_node_id) {
const gate_elements = func.runtime.ui.get_live_render_gate_elements_for_entry(options, entry);
if (!gate_elements.length) {
entry._used_parent_fallback = 'blocked_by_render_gate_parent';
func.runtime.ui.debug_xu_render_field(options, 'parent_gate_blocked_child_render', {
node_id: entry?.node_id || null,
parent_node_id: entry?.parent_node_id || null,
render_gate_parent_node_id: entry?.render_gate_parent_node_id || null,
panel_wrapper_ui_id: entry?.panel_wrapper_ui_id || null,
panel_ds_session: entry?.panel_ds_session || null,
});
return [];
}
}
const fallback_panel_wrapper = entry.panel_wrapper_ui_id
? func.runtime.ui.get_refresh_indexed_element_by_ui_id(options.SESSION_ID, entry.panel_wrapper_ui_id)
: null;
const fallback_panel_wrapper_node = func.runtime.ui.get_first_node(fallback_panel_wrapper);
if (fallback_panel_wrapper_node && entry.parent_node_id) {
const virtual_parent_matches = func.runtime.ui.find_live_xu_render_parent_by_node_data(options, entry, fallback_panel_wrapper_node);
if (virtual_parent_matches.length) {
parent_elements = virtual_parent_matches;
used_parent_fallback = 'panel_virtual_parent';
}
}
// Prefer a live element INSIDE the panel that runs on the panel's own
// datasource session. The panel WRAPPER element carries the OUTER session,
// so evaluating/rendering against it resolves the render expression (e.g.
// @open_modal_v) on the wrong datasource. querySelectorAll is document
// order, so the first session match is the panel content root.
if (fallback_panel_wrapper_node && entry.panel_ds_session != null) {
const session_match = Array.from(fallback_panel_wrapper_node.querySelectorAll('[xu-ui-id]')).find(function (el) {
const el_data = func.runtime.ui.get_data(el);
return el?.isConnected && el_data?.xuData && el_data.xuData.paramsP && el_data.xuData.paramsP.dsSessionP === entry.panel_ds_session;
});
if (session_match) {
parent_elements = [session_match];
used_parent_fallback = 'panel_session_element';
}
}
// Last resorts: the panel wrapper itself (outer session - may evaluate
// false, but still a mount point), then the embed screen root.
if (!parent_elements.length && fallback_panel_wrapper_node) {
parent_elements = func.runtime.ui._to_node_array(fallback_panel_wrapper);
if (parent_elements.length) {
used_parent_fallback = 'panel_wrapper';
}
}
if (!parent_elements.length) {
parent_elements = func.runtime.ui._to_node_array(func.runtime.ui.get_embed_screen_containers());
if (parent_elements.length) {
used_parent_fallback = 'embed_screen';
}
}
}
entry._used_parent_fallback = used_parent_fallback;
const search_roots = func.runtime.ui._to_node_array(options.$elm_to_search);
const search_root = search_roots.length ? search_roots : null;
const filtered_parent_elements = parent_elements.filter(function (parent_element) {
const parent_data = func.runtime.ui.get_data(parent_element);
return parent_element?.isConnected && parent_data?.xuData && func.runtime.ui.matches_refresh_search_root(parent_element, search_root);
});
func.runtime.ui.debug_xu_render_field(options, 'parent_elements', {
node_id: entry?.node_id || null,
parent_node_id: entry?.parent_node_id || null,
parent_element_ui_id: entry?.parent_element_ui_id || null,
panel_wrapper_ui_id: entry?.panel_wrapper_ui_id || null,
candidate_count: parent_elements.length,
filtered_count: filtered_parent_elements.length,
used_parent_fallback,
mount: func.runtime.ui.should_mount_xu_render_entry_at_screen_root(entry) ? 'screen_root' : 'logical_parent',
candidates: parent_elements.slice(0, 10).map(function (parent_element) {
const parent_data = func.runtime.ui.get_data(parent_element);
return {
tag: parent_element?.tagName || null,
ui_id: func.runtime.ui.get_attr(parent_element, 'xu-ui-id') || null,
node_id: parent_data?.xuData?.nodeid || parent_data?.xuData?.node?.id || null,
dsSession: parent_data?.xuData?.paramsP?.dsSessionP || null,
};
}),
});
return filtered_parent_elements;
};
func.runtime.ui.is_prog_ui_xu_render_live_under_parent = function (options, entry, parent_element) {
const live_elements = func.runtime.ui.get_refresh_indexed_elements_by_node_id(options.SESSION_ID, entry.node_id).toArray();
const parent_data = func.runtime.ui.get_data(parent_element);
const parent_params = parent_data?.xuData?.paramsP;
const root_mounted_overlay = func.runtime.ui.should_mount_xu_render_entry_at_screen_root(entry);
const matches_entry_scope = function (candidate_element, candidate_data) {
if (root_mounted_overlay) {
const candidate_params = candidate_data?.xuData?.paramsP;
if (
candidate_params?.dsSessionP === parent_params?.dsSessionP &&
candidate_data?.xuData?.recordid === parent_data?.xuData?.recordid
) {
return true;
}
}
return candidate_element.parentElement === parent_element || parent_element?.contains?.(candidate_element);
};
for (let live_index = 0; live_index < live_elements.length; live_index++) {
const live_element = live_elements[live_index];
const live_data = func.runtime.ui.get_data(live_element);
// pending_to_delete is NOT a liveness veto: it marks an element whose xu-render pass is
// already queued — that pass owns the node. Treating it as "missing" here raced the queued
// job and mounted a duplicate subtree (a second full copy of an open modal, painted over
// the first — the visible re-render/flick on open). The flag is transient (a 1s maintenance
// sweep clears it), so deferring recovery for a pending element can never wedge the node.
if (!live_element?.isConnected) {
continue;
}
if (matches_entry_scope(live_element, live_data)) {
return true;
}
}
// The refresh index keeps a single sticky slot per xu-ui-id, so content that was mounted by a
// pass which bypassed index registration (e.g. an earlier recovery render) stands connected in
// the DOM while being invisible to the index. The DOM is the primary truth: a connected element
// of this prog node inside its mount root means the node is NOT missing — recovering over it
// stacks another copy of the subtree.
const mount_root = root_mounted_overlay
? func.runtime.ui.get_first_node(func.runtime.ui.get_xu_render_screen_root_mount?.() || parent_element)
: parent_element;
const mount_children = mount_root?.children || [];
for (let child_index = 0; child_index < mount_children.length; child_index++) {
const child_element = mount_children[child_index];
if (!child_element?.isConnected || child_element.tagName === 'XURENDER') {
continue;
}
const child_data = func.runtime.ui.get_data(child_element);
const child_xu_data = child_data?.xuData;
const child_node_id = child_xu_data?.node?.id || child_xu_data?.node_org?.id || child_xu_data?.original_data_obj?.nodeP?.id;
if (child_node_id !== entry.node_id) {
continue;
}
if (matches_entry_scope(child_element, child_data)) {
return true;
}
}
return false;
};
func.runtime.ui.evaluate_prog_ui_xu_render_entry = async function (options, entry, parent_element) {
const parent_data = func.runtime.ui.get_data(parent_element);
const parent_xu_data = parent_data?.xuData;
const paramsP = parent_xu_data?.paramsP;
const attr_value =
typeof entry.attr_value !== 'undefined' ? entry.attr_value : func.runtime.ui.get_prog_ui_node_attribute_value(entry.node, entry.attr);
if (!paramsP || typeof attr_value === 'undefined') {
return false;
}
if (entry.attr === 'xu-exp:xu-render') {
const res = await func.expression.get(options.SESSION_ID, attr_value, paramsP.dsSessionP, 'UI Property EXP', parent_xu_data.recordid);
return await func.common.get_cast_val(options.SESSION_ID, 'refresh missing progUi xu-render', 'xu-render', 'bool', res.result);
}
if (typeof attr_value === 'string' && attr_value.includes('@')) {
const res = await func.expression.get(options.SESSION_ID, attr_value, paramsP.dsSessionP, 'UI Property EXP', parent_xu_data.recordid);
return await func.common.get_cast_val(options.SESSION_ID, 'refresh missing progUi xu-render', 'xu-render', 'bool', res.result);
}
return await func.common.get_cast_val(options.SESSION_ID, 'refresh missing progUi xu-render', 'xu-render', 'bool', attr_value);
};
func.runtime.ui.render_missing_prog_ui_xu_render_entry = async function (options, entry, parent_element, current_job) {
const parent_data = func.runtime.ui.get_data(parent_element);
const parent_xu_data = parent_data?.xuData;
const paramsP = parent_xu_data?.paramsP;
if (!paramsP) {
return current_job;
}
const should_render = await func.runtime.ui.evaluate_prog_ui_xu_render_entry(options, entry, parent_element);
func.runtime.ui.debug_xu_render_field(options, 'render_missing_evaluated', {
node_id: entry?.node_id || null,
parent_node_id: entry?.parent_node_id || null,
parent_element_ui_id: entry?.parent_element_ui_id || null,
panel_wrapper_ui_id: entry?.panel_wrapper_ui_id || null,
used_parent_fallback: entry?._used_parent_fallback || null,
mount: func.runtime.ui.should_mount_xu_render_entry_at_screen_root(entry) ? 'screen_root' : 'logical_parent',
should_render,
});
if (!should_render) {
return current_job;
}
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] refresh_missing_prog_ui_xu_render ' +
JSON.stringify({
fields: options.fields_arr || [],
node_id: entry.node_id,
parent_node_id: entry.parent_node_id || null,
key: entry.key,
mount: func.runtime.ui.should_mount_xu_render_entry_at_screen_root(entry) ? 'screen_root' : 'logical_parent',
}),
);
} catch (e) {}
const node_to_render = structuredClone(entry.node);
if (typeof entry.attr_value !== 'undefined' && typeof node_to_render?.attributes?.[entry.attr] === 'undefined') {
node_to_render.attributes = node_to_render.attributes || {};
node_to_render.attributes[entry.attr] = entry.attr_value;
}
let render_parent_element = parent_element;
if (func.runtime.ui.should_mount_xu_render_entry_at_screen_root(entry)) {
render_parent_element = func.runtime.ui.get_xu_render_screen_root_mount() || parent_element;
}
const new_$div = await func.runtime.render.render_ui_tree(
options.SESSION_ID,
func.runtime.ui._wrap_matches([render_parent_element]),
node_to_render,
parent_xu_data.iterate_info ? { iterate_info: parent_xu_data.iterate_info } : null,
paramsP,
current_job,
null,
entry.key,
null,
entry.parent_node || parent_xu_data.node_org || parent_xu_data.node || null,
null,
parent_xu_data.$root_container || func.runtime.ui.get_embed_screen_containers(),
);
// Recovery output must be lifecycle-equivalent to job-rendered content. The xu-render job
// path stamps the swapped-in content with the gate bookkeeping (xuAttributes carrying the
// xu-exp:xu-render expression, node identity, paramsP) — that stamping is what registers the
// element in the field-dependency index. Without it a recovered element is a lifecycle
// orphan: the gate field's next change reaches nobody (modal that can never close) and the
// index/liveness checks can't see the copy (another recovery stacks a duplicate over it).
const new_data = func.runtime.ui.get_data(new_$div);
if (new_data) {
new_data.xuData = new_data.xuData || {};
if (!new_data.xuData.node) {
new_data.xuData.node = node_to_render;
}
if (!new_data.xuData.node_org) {
new_data.xuData.node_org = node_to_render;
}
if (!new_data.xuData.ui_type) {
new_data.xuData.ui_type = node_to_render?.tagName || 'div';
}
if (!new_data.xuData.debug_info) {
new_data.xuData.debug_info = {};
}
if (!new_data.xuData.paramsP) {
new_data.xuData.paramsP = paramsP;
}
if (typeof new_data.xuData.recordid === 'undefined' && typeof parent_xu_data.recordid !== 'undefined') {
new_data.xuData.recordid = parent_xu_data.recordid;
}
if (xu_isEmpty(new_data.xuAttributes)) {
new_data.xuAttributes = { ...(node_to_render?.attributes || {}) };
}
}
// Identity contract at the recovery mount: the element just rendered is now the single owner
// of this node instance. Any copy an earlier race left behind (same xu-ui-id, connected)
// would otherwise stay stacked under the new one — one extra full copy per recovery pass.
const new_ui_id = func.runtime.ui.get_attr?.(new_$div, 'xu-ui-id');
if (new_ui_id && func.runtime.ui.reconcile_xu_ui_id_duplicates) {
func.runtime.ui.reconcile_xu_ui_id_duplicates(options.SESSION_ID, new_ui_id, new_$div);
}
func.runtime.ui.mark_refresh_index_dirty?.(options.SESSION_ID);
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_missing_prog_ui_xu_render_used');
return current_job;
};
func.runtime.ui.refresh_missing_prog_ui_xu_render_nodes = async function (options, current_job) {
let panels_obj = null;
try {
const $embed_container = func.runtime.ui.get_embed_screen_containers();
panels_obj = await func.UI.utils.get_panels_wrapper_from_dom(options.SESSION_ID, $embed_container, true);
} catch (error) {
func.runtime.ui.debug_xu_render_field(options, 'panel_collection_error', {
error: error?.message || String(error),
});
panels_obj = func.runtime.ui.get_cached_refresh_panel_wrappers(options.SESSION_ID, true);
}
func.runtime.ui.debug_xu_render_field(options, 'refresh_missing_start', {
panel_count: Object.keys(panels_obj || {}).length,
});
const entries = func.runtime.ui.collect_prog_ui_xu_render_entries_for_fields(options, panels_obj);
const rendered_keys = new Set();
for (let entry_index = 0; entry_index < entries.length; entry_index++) {
const entry = entries[entry_index];
const parent_elements = func.runtime.ui.get_prog_ui_xu_render_parent_elements(options, entry);
for (let parent_index = 0; parent_index < parent_elements.length; parent_index++) {
const parent_element = parent_elements[parent_index];
const parent_ui_id = func.runtime.ui.get_attr(parent_element, 'xu-ui-id') || 'root';
const render_key = `${parent_ui_id}::${entry.node_id}`;
if (rendered_keys.has(render_key)) {
continue;
}
if (func.runtime.ui.is_prog_ui_xu_render_live_under_parent(options, entry, parent_element)) {
continue;
}
current_job = await func.runtime.ui.render_missing_prog_ui_xu_render_entry(options, entry, parent_element, current_job);
rendered_keys.add(render_key);
}
}
return current_job;
};
func.runtime.ui.collect_refresh_selectors = function (options) {
let selectors = func.runtime.ui.collect_refresh_selectors_from_index(options);
if (!xu_isEmpty(selectors)) {
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_selector_index_used');
} else {
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_selector_scan_used');
selectors = func.runtime.ui.collect_refresh_selectors_by_scan(options);
}
if (xu_isEmpty(selectors) && typeof options.dsSession_changed !== 'undefined') {
const relaxed_selectors = func.runtime.ui.collect_refresh_selectors_by_scan({
...options,
dsSession_changed: undefined,
});
const render_selectors = {};
for (const [selector_id, selector_entry] of Object.entries(relaxed_selectors || {})) {
const render_attrs = (selector_entry.attributes || []).filter(function (attr) {
return attr === 'xu-exp:xu-render' || attr === 'xu-render';
});
if (!render_attrs.length) continue;
render_selectors[selector_id] = {
...selector_entry,
attributes: render_attrs,
};
}
if (!xu_isEmpty(render_selectors)) {
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] refresh_selector_relaxed_xu_render ' +
JSON.stringify({
fields: options.fields_arr || [],
dsSession_changed: options.dsSession_changed,
selectors: Object.keys(render_selectors),
}),
);
} catch (e) {}
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_selector_relaxed_xu_render_used');
selectors = func.runtime.ui.merge_refresh_selector_sets(selectors, render_selectors);
}
}
const prog_ui_render_selectors = func.runtime.ui.collect_refresh_xu_render_selectors_from_prog_ui(options);
selectors = func.runtime.ui.merge_refresh_selector_sets(selectors, prog_ui_render_selectors);
return selectors;
};
func.runtime.ui.build_refresh_job_obj = function (options, elem_key, elem_val, extra = {}) {
const elm_data = func.runtime.ui.get_data(elem_val.$elm);
return {
ui_type: elm_data?.xuData?.ui_type,
SESSION_ID: options.SESSION_ID,
fields_arr: options.fields_arr,
elem_key,
node_id: elm_data?.xuData?.nodeid,
key: elm_data?.xuData?.key,
key_path: elm_data?.xuData?.key_path,
recordid: elm_data?.xuData?.recordid,
parent_element_ui_id: elm_data?.xuData?.parent_element_ui_id,
prog_id: elm_data?.xuData?.paramsP?.prog_id,
elem_val: {
attributes: [...(elem_val.attributes || [])],
},
...extra,
};
};
func.runtime.ui.queue_refresh_execute_job = async function (options, elem_key, elem_val, type, current_job) {
const elm_data = func.runtime.ui.get_data(elem_val.$elm);
if (!elm_data?.xuData) {
return current_job;
}
try {
const obj = func.runtime.ui.build_refresh_job_obj(options, elem_key, elem_val);
const next_job = await func.UI.worker.add_to_queue(options.SESSION_ID, 'gui event', type, obj, current_job, elem_val.$elm);
return next_job;
} catch (error) {
console.error('[xuda-runtime] caught xuda_runtime.browser.refresh.scan.js:1016:', error);
return current_job;
}
};
func.runtime.ui.queue_refresh_render_job = async function (options, elem_key, elem_val, attr_value, current_job) {
const elm_data = func.runtime.ui.get_data(elem_val?.$elm);
if (!elm_data?.xuData?.ui_type) {
return current_job;
}
const obj = func.runtime.ui.build_refresh_job_obj(options, elem_key, elem_val, { attr_value });
const next_job = await func.UI.worker.add_to_queue(
options.SESSION_ID,
'gui event',
'execute_xu_render_attributes',
obj,
current_job,
elem_val.$elm,
elm_data.xuData.paramsP.dsSessionP,
);
if (glb.DEBUG_MODE) {
console.info('execute_xu_render_attributes', obj);
}
return next_job;
};
func.runtime.ui.mark_refresh_pending_delete = function (elm_input) {
const elm = func.runtime.ui.get_first_node(elm_input);
const session_id = func.runtime.ui.resolve_refresh_index_session_id ? func.runtime.ui.resolve_refresh_index_session_id(elm) : func.runtime.ui.get_data(elm)?.xuData?.SESSION_ID;
const descendants = elm?.querySelectorAll?.('[xu-ui-id]') || [];
for (let index = 0; index < descendants.length; index++) {
const val = descendants[index];
const element_data = func.runtime.ui.get_data(val);
if (element_data?.xuData) {
element_data.xuData.pending_to_delete = true;
if (func.UI?.worker?.mark_pending_delete_element && session_id) {
func.UI.worker.mark_pending_delete_element(session_id, val);
}
}
}
if (func.UI?.worker?.mark_pending_delete_session && session_id) {
func.UI.worker.mark_pending_delete_session(session_id);
}
return elm_input;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
// Browser-only xu-for refresh helpers live here so generic refresh scanning can stay focused.
func.runtime.ui.prog_ui_xu_for_dependency_cache = func.runtime.ui.prog_ui_xu_for_dependency_cache || new WeakMap();
func.runtime.ui.prog_ui_xu_for_fields_cache = func.runtime.ui.prog_ui_xu_for_fields_cache || new Map();
func.runtime.ui.get_refresh_parent_element_ui_id = function ($elm) {
const elm_data = func.runtime.ui.get_data($elm);
if (elm_data?.xuPanelData) {
return elm_data.xuPanelData.parent_element_ui_id;
}
return elm_data?.xuData?.parent_element_ui_id;
};
func.runtime.ui.resolve_refresh_xu_for_item_id = function ($elm) {
const elm_data = func.runtime.ui.get_data($elm);
if (elm_data?.xuPanelData) {
return elm_data.xuPanelData.node.id;
}
return elm_data?.xuData?.nodeid;
};
func.runtime.ui.build_refresh_element_value = function ($elm) {
const elem_val = {
attributes: Object.keys(func.runtime.ui.get_data($elm)?.xuAttributes || {}),
$elm,
};
return elem_val;
};
func.runtime.ui.build_xu_for_refresh_job_obj = function (options, elem_key, $elm, xu_for_item_id) {
const elm_data = func.runtime.ui.get_data($elm);
return {
ui_type: elm_data.xuData.ui_type,
SESSION_ID: options.SESSION_ID,
fields_arr: options.fields_arr,
elem_key,
elem_val: {},
xu_for_item_id,
};
};
func.runtime.ui.queue_xu_for_refresh = async function (options, $elm, xu_for_item_id, current_job, debug_label, debug_context) {
const elm_data = func.runtime.ui.get_data($elm);
if (options.avoid_xu_for_refresh || !$elm?.length || !elm_data?.xuData) {
return current_job;
}
const elem_key = func.runtime.ui.get_attr($elm, 'xu-ui-id');
const obj = func.runtime.ui.build_xu_for_refresh_job_obj(options, elem_key, $elm, xu_for_item_id);
await func.UI.worker.add_to_queue(options.SESSION_ID, 'gui event', 'execute_xu_for', obj, current_job, $elm, elm_data.xuData.paramsP.dsSessionP);
if (glb.DEBUG_MODE) {
console.info(debug_label || 'execute_xu_for', obj, debug_context);
}
return current_job;
};
func.runtime.ui.collect_prog_ui_xu_for_fields = function (attr_val) {
const cache_key = typeof attr_val === 'string' ? `str:${attr_val}` : `obj:${JSON.stringify(attr_val || {})}`;
const cached_fields = func.runtime.ui.prog_ui_xu_for_fields_cache.get(cache_key);
if (cached_fields) {
return cached_fields;
}
const fields = new Set();
const add_fields = function (values) {
values?.forEach?.(function (field_id) {
fields.add(field_id);
});
};
add_fields(func.runtime.ui.extract_expression_refresh_fields({}, attr_val, true));
add_fields(func.runtime.ui.extract_expression_refresh_fields({}, attr_val, false));
const result = [...fields].filter(Boolean);
func.runtime.ui.prog_ui_xu_for_fields_cache.set(cache_key, result);
return result;
};
func.runtime.ui.add_prog_ui_xu_for_dependency = function (index, field_id, entry) {
if (!field_id) {
return;
}
if (!index[field_id]) {
index[field_id] = {};
}
const entry_key = `${entry.parent_node_id || ''}::${entry.xu_for_item_id}`;
index[field_id][entry_key] = entry;
};
func.runtime.ui.collect_prog_ui_xu_for_dependencies = function (progUi, index = {}, parent_node_id) {
if (!Array.isArray(progUi)) {
return index;
}
for (const item of progUi) {
if (!xu_isEmpty(item?.attributes)) {
const attribute_keys = Object.keys(item.attributes);
for (let attr_index = 0; attr_index < attribute_keys.length; attr_index++) {
const attr = attribute_keys[attr_index];
const val = item.attributes[attr];
if (attr !== 'xu-exp:xu-for' && attr !== 'xu-for') {
continue;
}
const fields = func.runtime.ui.collect_prog_ui_xu_for_fields(val);
for (const field_id of fields) {
func.runtime.ui.add_prog_ui_xu_for_dependency(index, field_id, {
parent_node_id,
xu_for_item_id: item.id,
});
}
}
}
if (item?.children) {
func.runtime.ui.collect_prog_ui_xu_for_dependencies(item.children, index, item.id);
}
}
return index;
};
func.runtime.ui.get_prog_ui_xu_for_dependencies = function (progUi) {
if (!Array.isArray(progUi)) {
return {};
}
let cached_dependencies = func.runtime.ui.prog_ui_xu_for_dependency_cache.get(progUi);
if (cached_dependencies) {
return cached_dependencies;
}
cached_dependencies = func.runtime.ui.collect_prog_ui_xu_for_dependencies(progUi, {});
func.runtime.ui.prog_ui_xu_for_dependency_cache.set(progUi, cached_dependencies);
return cached_dependencies;
};
func.runtime.ui.merge_prog_ui_xu_for_dependencies = function (target_index, source_index) {
if (xu_isEmpty(source_index)) {
return target_index;
}
const field_ids = Object.keys(source_index);
for (let field_index = 0; field_index < field_ids.length; field_index++) {
const field_id = field_ids[field_index];
const entry_obj = source_index[field_id];
if (!target_index[field_id]) {
target_index[field_id] = {};
}
const entry_keys = Object.keys(entry_obj || {});
for (let entry_index = 0; entry_index < entry_keys.length; entry_index++) {
const entry_key = entry_keys[entry_index];
const entry = entry_obj[entry_key];
target_index[field_id][entry_key] = entry;
}
}
return target_index;
};
func.runtime.ui.rebuild_refresh_xu_for_index = async function (SESSION_ID, panels_obj, $xu_embed_container) {
const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
if (!state) {
return {};
}
const index = {};
const $embed_container = $xu_embed_container?.length ? $xu_embed_container : func.runtime.ui.get_embed_screen_containers();
const root_prog_ui = func.runtime.ui.get_data($embed_container)?.xuData?.screenInfo?.progUi;
if (root_prog_ui) {
func.runtime.ui.merge_prog_ui_xu_for_dependencies(index, func.runtime.ui.get_prog_ui_xu_for_dependencies(root_prog_ui));
}
const resolved_panels_obj = panels_obj || (await func.UI.utils.get_panels_wrapper_from_dom(SESSION_ID, $embed_container, true));
for (const panel_val of Object.values(resolved_panels_obj || {})) {
func.runtime.ui.merge_prog_ui_xu_for_dependencies(index, func.runtime.ui.get_prog_ui_xu_for_dependencies(panel_val?.progUi));
}
state.xu_for_index = index;
state.xu_for_dirty = false;
return index;
};
func.runtime.ui.collect_refresh_xu_for_entries = async function (options, panels_obj, $xu_embed_container) {
const state = func.runtime.ui.ensure_refresh_dependency_state(options.SESSION_ID);
if (!state) {
return {
entries: [],
missing_fields: [...(options.fields_arr || [])],
panels_obj: panels_obj || {},
};
}
let resolved_panels_obj = panels_obj;
if (state.xu_for_dirty) {
if (!resolved_panels_obj) {
const $embed_container = $xu_embed_container?.length ? $xu_embed_container : func.runtime.ui.get_embed_screen_containers();
resolved_panels_obj = await func.UI.utils.get_panels_wrapper_from_dom(options.SESSION_ID, $embed_container, true);
}
await func.runtime.ui.rebuild_refresh_xu_for_index(options.SESSION_ID, resolved_panels_obj, $xu_embed_container);
}
const entries = {};
const missing_fields = [];
const fields_arr = options.fields_arr || [];
for (let field_index = 0; field_index < fields_arr.length; field_index++) {
const field_id = fields_arr[field_index];
const field_entries = state.xu_for_index?.[field_id];
if (xu_isEmpty(field_entries)) {
missing_fields.push(field_id);
continue;
}
const entry_keys = Object.keys(field_entries);
for (let entry_index = 0; entry_index < entry_keys.length; entry_index++) {
const entry_key = entry_keys[entry_index];
const entry = field_entries[entry_key];
entries[`${field_id}::${entry_key}`] = entry;
}
}
return {
entries: Object.values(entries),
missing_fields,
panels_obj: resolved_panels_obj || {},
};
};
func.runtime.ui.iterate_refresh_field_in_prog_ui = async function (options, progUi, field_id, refreshed_ids = [], panel_val, current_job) {
const dependencies = func.runtime.ui.get_prog_ui_xu_for_dependencies(progUi);
const field_entries = Object.values(dependencies?.[field_id] || {});
for (const entry of field_entries) {
const $elem = func.runtime.ui.get_refresh_indexed_elements_by_node_id(options.SESSION_ID, entry.parent_node_id);
if (!$elem?.length) {
continue;
}
const parent_element_ui_id = func.runtime.ui.get_attr($elem, 'xu-ui-id');
const refresh_key = parent_element_ui_id && entry.xu_for_item_id ? `${parent_element_ui_id}::${entry.xu_for_item_id}` : parent_element_ui_id;
const is_refreshed = refreshed_ids instanceof Set ? refreshed_ids.has(refresh_key) : refreshed_ids.includes(refresh_key);
if (is_refreshed) {
continue;
}
await func.runtime.ui.queue_xu_for_refresh(options, $elem, entry.xu_for_item_id, current_job, 'node execute_xu_for', panel_val);
if (refreshed_ids instanceof Set) {
refreshed_ids.add(refresh_key);
} else {
refreshed_ids.push(refresh_key);
}
}
return current_job;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
// Browser-only xu-attribute refresh helpers live here so screen refresh orchestration can stay focused.
func.runtime.ui.refresh_xu_attributes = async function (options) {
if (!options.ignore_screen_blocker && options.trigger !== 'click' && !xu_isEmpty(SCREEN_BLOCKER_OBJ)) {
setTimeout(() => {
func.runtime.ui.refresh_xu_attributes(options);
}, 100);
return;
}
const perf_end = func.runtime?.perf?.start?.(options.SESSION_ID, 'refresh_xu_attributes');
UI_WORKER_OBJ.cache = {};
try {
// if (glb.DEBUG_MODE) {
// console.info('========= xu-attributes refresh info ==============');
// console.info('fields_arr:', options.fields_arr);
// }
let new_job = options.jobNoP;
let selectors = func.runtime.ui.collect_refresh_selectors(options);
const xu_for_selectors = [];
const refreshed_ids = new Set();
const selector_keys = Object.keys(selectors);
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_selector_batches');
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_selector_total', selector_keys.length);
for (let selector_index = 0; selector_index < selector_keys.length; selector_index++) {
const elem_key = selector_keys[selector_index];
const elem_val = selectors[elem_key];
if (!elem_val) continue;
const $elm = elem_val.$elm;
const elm_data = func.runtime.ui.get_data($elm);
const xuData = elm_data?.xuData;
const xuAttributes = elm_data?.xuAttributes;
if (!xuData || xuData.pending_to_delete) continue;
const attr_list = elem_val.attributes || [];
const has_xu_exp_render = attr_list.includes('xu-exp:xu-render');
const has_xu_for = attr_list.includes('xu-exp:xu-for') || attr_list.includes('xu-for');
let performed_render;
if (!xuAttributes) continue;
if (has_xu_exp_render) {
const res = await func.expression.get(options.SESSION_ID, xuAttributes['xu-exp:xu-render'], xuData.paramsP.dsSessionP, 'UI Property EXP', xuData.recordid);
const attr_value = await func.common.get_cast_val(options.SESSION_ID, 'refresh xu-attributes', 'xu-render', 'bool', res.result);
const _elm_node = func.runtime.ui.get_first_node($elm);
if (!attr_value && _elm_node?.tagName === 'XURENDER') continue;
if (attr_value && _elm_node?.tagName !== 'XURENDER') {
new_job = await func.runtime.ui.queue_refresh_execute_job(options, elem_key, elem_val, 'execute_xu_all_attributes', new_job);
continue;
}
new_job = await func.runtime.ui.queue_refresh_render_job(options, elem_key, elem_val, attr_value, options.jobNoP);
performed_render = true;
func.runtime.ui.mark_refresh_pending_delete($elm);
}
const _elm_tag_node = func.runtime.ui.get_first_node($elm);
if (performed_render || _elm_tag_node?.tagName === 'XURENDER') continue;
if (has_xu_for) {
xu_for_selectors.push(elem_val);
continue;
}
if (xuData.ui_type === 'xu-widget') {
new_job = await func.runtime.ui.queue_refresh_execute_job(options, elem_key, elem_val, 'execute_xu_widget', new_job);
} else {
new_job = await func.runtime.ui.queue_refresh_execute_job(options, elem_key, elem_val, 'execute_xu_all_attributes', new_job);
}
}
if (func.runtime.ui.refresh_missing_prog_ui_xu_render_nodes) {
new_job = await func.runtime.ui.refresh_missing_prog_ui_xu_render_nodes(options, new_job);
}
for (let xu_for_index = 0; xu_for_index < xu_for_selectors.length; xu_for_index++) {
const elem_val = xu_for_selectors[xu_for_index];
const $elm = elem_val?.$elm;
const xuData = func.runtime.ui.get_data($elm)?.xuData;
if (!xuData || xuData.pending_to_delete) continue;
const parent_element_ui_id = func.runtime.ui.get_refresh_parent_element_ui_id($elm);
const xu_for_item_id = func.runtime.ui.resolve_refresh_xu_for_item_id($elm);
const refresh_key = parent_element_ui_id && xu_for_item_id ? `${parent_element_ui_id}::${xu_for_item_id}` : parent_element_ui_id;
if (!parent_element_ui_id || refreshed_ids.has(refresh_key)) {
continue;
}
let _$elem = func.runtime.ui.get_refresh_indexed_element_by_ui_id(options.SESSION_ID, parent_element_ui_id);
if (_$elem && !_$elem.length) _$elem = func.runtime.ui._wrap_matches([_$elem]);
await func.runtime.ui.queue_xu_for_refresh(options, _$elem, xu_for_item_id, new_job, 'execute_xu_for');
refreshed_ids.add(refresh_key);
}
const $xu_embed_container = func.runtime.ui.get_embed_screen_containers();
const refresh_state = func.runtime.ui.ensure_refresh_dependency_state(options.SESSION_ID);
const preload_panels_obj = refresh_state?.xu_for_dirty ? await func.UI.utils.get_panels_wrapper_from_dom(options.SESSION_ID, $xu_embed_container, true) : null;
let { entries: xu_for_entries, missing_fields, panels_obj } = await func.runtime.ui.collect_refresh_xu_for_entries(options, preload_panels_obj, $xu_embed_container);
if (missing_fields.length && xu_isEmpty(panels_obj)) {
panels_obj = await func.UI.utils.get_panels_wrapper_from_dom(options.SESSION_ID, $xu_embed_container, true);
}
for (let entry_index = 0; entry_index < xu_for_entries.length; entry_index++) {
const entry = xu_for_entries[entry_index];
const $elem = func.runtime.ui.get_refresh_indexed_elements_by_node_id(options.SESSION_ID, entry.parent_node_id);
if (!$elem?.length) {
continue;
}
const indexed_parent_element_ui_id = func.runtime.ui.get_attr($elem, 'xu-ui-id');
const indexed_refresh_key = indexed_parent_element_ui_id && entry.xu_for_item_id ? `${indexed_parent_element_ui_id}::${entry.xu_for_item_id}` : indexed_parent_element_ui_id;
if (!indexed_parent_element_ui_id || refreshed_ids.has(indexed_refresh_key)) {
continue;
}
await func.runtime.ui.queue_xu_for_refresh(options, $elem, entry.xu_for_item_id, new_job, 'indexed execute_xu_for', entry);
refreshed_ids.add(indexed_refresh_key);
}
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_xu_for_entries_total', xu_for_entries.length);
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_xu_for_missing_fields_total', missing_fields.length);
for (let missing_index = 0; missing_index < missing_fields.length; missing_index++) {
const field_id = missing_fields[missing_index];
if ($xu_embed_container.length) {
const progUi = func.runtime.ui.get_data($xu_embed_container)?.xuData?.screenInfo?.progUi;
if (progUi) {
await func.runtime.ui.iterate_refresh_field_in_prog_ui(options, progUi, field_id, refreshed_ids, null, new_job);
}
}
const panel_ids = Object.keys(panels_obj || {});
for (let panel_index = 0; panel_index < panel_ids.length; panel_index++) {
const panel_val = panels_obj[panel_ids[panel_index]];
await func.runtime.ui.iterate_refresh_field_in_prog_ui(options, panel_val.progUi, field_id, refreshed_ids, panel_val, new_job);
}
}
func.events.delete_job(options.SESSION_ID, options.jobNoP);
// if (glb.DEBUG_MODE) {
// console.info('===================================================');
// }
} finally {
perf_end?.();
}
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
// [xu-preserve] one-time load marker — confirms THIS build (refresh-screen child-preserve + data-xu-preserve
// marker, build-400 parity) is the runtime actually running in the page. Temporary debug.
try {
const get_runtime_script_cache_tag = function () {
if (typeof document === 'undefined') {
return '';
}
const script_nodes = [];
if (document.currentScript?.src) {
script_nodes.push(document.currentScript);
}
if (document.scripts?.length) {
script_nodes.push(...Array.from(document.scripts).reverse());
}
for (let index = 0; index < script_nodes.length; index++) {
const src = script_nodes[index]?.src || '';
if (!src || !src.includes('xuda-runtime-bundle')) {
continue;
}
try {
const runtime_script_url = new URL(src, document.baseURI);
const runtime_cache_tag = runtime_script_url.searchParams.get('runtime_cache');
if (runtime_cache_tag) {
return runtime_cache_tag;
}
} catch (error) {}
}
return '';
};
if (typeof globalThis !== 'undefined') {
const runtime_cache_tag = get_runtime_script_cache_tag();
if (runtime_cache_tag) {
globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ = runtime_cache_tag;
}
}
if (typeof window !== 'undefined' && !window.__xu_preserve_build_logged) {
window.__xu_preserve_build_logged = true;
console.log(
'%c[xuda-runtime:refresh] ' +
JSON.stringify({
version: 'runtime-refresh-20260701-xu-render-parent-gate',
runtime_build: 484,
changes: [
'refresh-coalesce',
'status-tick-no-refresh',
'props-gate',
'child-preserve',
'xu-panel-param-expression-gate',
'native-js-import-cache-bust',
'datasource-refresh-log-restored',
'avoid-refresh-skips-refresh-screen',
'datasource-refresh-cause-logs',
'event-avoid-refresh-option',
'nested-event-avoid-refresh-option',
'out-param-avoid-refresh',
'output-field-avoid-refresh',
'output-param-attributes-only',
'api-set-field-value-attributes-only',
'api-set-field-value-deferred-refresh',
'synthetic-ref-field-no-panel-rerender',
'framework-root-preserve',
'preserved-reattach-lifecycle-event',
'xu-render-relaxed-selector',
'prog-ui-xu-render-selector',
'missing-prog-ui-xu-render-render',
'data-root-expression-fields',
'panel-prog-ui-xu-render-deps',
'panel-prog-ui-xu-render-context',
'live-node-org-xu-render-deps',
'open-modal-xu-render-probe',
'missing-render-parent-fallback',
'missing-render-panel-session-element',
'missing-render-logical-root-overlay',
'save-asset-event-trace',
'set-data-before-render-snapshot',
'pouch-replication-stat-off-without-replication',
'runtime-module-cache-tag-from-script-scan',
'custom-header-waits-inline-module-scripts',
'custom-header-recursive-executable-clone',
'tailwind-plugin-init-script-textcontent',
'tailwind-plugin-init-eval-fallback',
'tailwind-plugin-post-setup-refresh',
'custom-header-inline-module-blob-url',
'custom-header-inline-module-dynamic-import',
'custom-header-tailwind-refresh-retries',
'tailwind-component-theme-alpha-normalize',
'tailwind-theme-alpha-normalize-before-refresh',
'tailwind-addcomponents-captured-css',
'plugin-runtime-manifest-resource-path',
'plugin-runtime-import-failure-logged-continue',
'tailwind-refresh-coalesced',
'doc-ws-http-fallback',
'doc-ws-timeout',
'doc-ws-fallback-log-once',
'prog-ui-raw-attributes-initial-render',
'tagname-node-attribute-phases',
'ui-plugin-dom-element-target',
'xu-render-parent-gate',
],
}),
'color:#1d69db;font-weight:bold',
);
}
} catch (e) {}
// Browser-only screen refresh orchestration lives here so the browser runtime can be split by concern.
func.runtime.ui.prog_doc_refresh_dependency_cache = func.runtime.ui.prog_doc_refresh_dependency_cache || new WeakMap();
func.runtime.ui.panel_refresh_dependency_cache = func.runtime.ui.panel_refresh_dependency_cache || new WeakMap();
func.runtime.ui.extract_refresh_fields_from_text = function (text) {
const fields = new Set();
const str = typeof text === 'string' ? text : JSON.stringify(text || {});
if (!str) {
return fields;
}
for (const match of str.matchAll(/@([A-Za-z0-9_$.-]+)/g)) {
if (match?.[1]) {
fields.add(match[1]);
}
}
return fields;
};
func.runtime.ui.get_prog_doc_refresh_dependencies = function (prog_doc) {
if (!prog_doc || typeof prog_doc !== 'object') {
return {
prog_fields: new Set(),
xu_for_fields: new Set(),
};
}
let cache = func.runtime.ui.prog_doc_refresh_dependency_cache.get(prog_doc);
if (cache) {
return cache;
}
const prog_fields = new Set();
const prog_data_source_str = JSON.stringify(prog_doc.progDataSource || {});
for (const match of prog_data_source_str.matchAll(/@([A-Za-z0-9_$.-]+)/g)) {
if (match?.[1]) {
prog_fields.add(match[1]);
}
}
// NOTE: datasource "watch fields" are NOT handled here. Adding them to prog_fields
// only makes validate_change() take the shallow panel-rerender branch, which does
// NOT re-fire on_load/screen_ready (those are gated off on a refresh, see
// xuda_datasource.js run_on_load_events / schedule_panel_on_load_events). Watch
// fields must re-run the lifecycle so screen_ready-derived data (e.g. a virtual
// field) rebuilds — that is done in func.datasource.update (fire_watch_field_lifecycle).
const xu_for_index = func.UI.utils.get_prog_ui_attribute_index?.(prog_doc.progUi, 'xu-for') || {};
const xu_for_fields = new Set(Object.keys(xu_for_index));
cache = {
prog_fields,
xu_for_fields,
};
func.runtime.ui.prog_doc_refresh_dependency_cache.set(prog_doc, cache);
return cache;
};
func.runtime.ui.get_panel_refresh_dependencies = function (panelXuAttributes) {
if (!panelXuAttributes || typeof panelXuAttributes !== 'object') {
return {
program_fields: new Set(),
parameter_entries: [],
};
}
let cache = func.runtime.ui.panel_refresh_dependency_cache.get(panelXuAttributes);
if (cache) {
return cache;
}
const program_fields = func.runtime.ui.extract_refresh_fields_from_text(panelXuAttributes['xu-exp:program']);
const parameter_entries = [];
const panel_attr_keys = Object.keys(panelXuAttributes);
for (let attr_index = 0; attr_index < panel_attr_keys.length; attr_index++) {
const attr = panel_attr_keys[attr_index];
const value = panelXuAttributes[attr];
const match = attr.match(/xu-exp:(\w+)/);
if (!match || match[1] === 'program') {
continue;
}
parameter_entries.push({
parameter_in_field_id: match[1],
value_str: typeof value === 'string' ? value : JSON.stringify(value || {}),
});
}
cache = {
program_fields,
parameter_entries,
};
func.runtime.ui.panel_refresh_dependency_cache.set(panelXuAttributes, cache);
return cache;
};
// ---- build-400 parity: preserve app-injected content across a panel re-render ----
// Build 400 never blanket-removed a container's children, so DOM an app imperatively appended inside a
// runtime container survived a datasource refresh.
// The current runtime's empty()/remove() wipes regressed that and can destroy mounted native framework
// roots. Preserve explicit `data-xu-preserve` roots and known framework roots before a wipe, keyed by
// the nearest runtime ancestor's stable node id (data-xuda-node-id = template nodeP.id). The mounted
// framework app survives the DOM move (no unmount), so its state/reactivity is preserved.
func.runtime.ui.get_preservable_child_reason = function (el) {
try {
if (!el || el.nodeType !== 1) return '';
if (el.hasAttribute && el.hasAttribute('data-xu-preserve')) return 'data-xu-preserve';
if (el.__vue_app__ || el.__vueParentComponent || el._vnode) return 'vue-root';
if (el._reactRootContainer) return 'react-root';
const own_props = Object.getOwnPropertyNames(el);
for (let i = 0; i < own_props.length; i++) {
const prop = own_props[i];
if (
typeof prop === 'string' &&
(prop.indexOf('__reactContainer$') === 0 || prop.indexOf('__reactFiber$') === 0)
) {
return 'react-root';
}
}
} catch (e) {}
return '';
};
func.runtime.ui.detach_preserved_children = function ($container) {
const rescued = [];
try {
const root = func.runtime.ui.get_first_node($container);
if (!root || !root.querySelectorAll) return rescued;
const candidates = [];
const seen = new Set();
const add_candidate = function (el, reason) {
if (!el || el === root || seen.has(el)) return;
seen.add(el);
candidates.push({ node: el, reason: reason || func.runtime.ui.get_preservable_child_reason(el) || 'preserve' });
};
const marked = root.querySelectorAll('[data-xu-preserve]');
for (let i = 0; i < marked.length; i++) add_candidate(marked[i], 'data-xu-preserve');
const all_children = root.querySelectorAll('*');
for (let i = 0; i < all_children.length; i++) {
const el = all_children[i];
const reason = func.runtime.ui.get_preservable_child_reason(el);
if (reason) add_candidate(el, reason);
}
for (let i = 0; i < candidates.length; i++) {
const el = candidates[i].node;
// only the outermost preservable node when preserved roots nest
let parent = el.parentElement;
let nested = false;
while (parent && parent !== root) {
if (seen.has(parent)) {
nested = true;
break;
}
parent = parent.parentElement;
}
if (nested) continue;
// anchor = nearest runtime ancestor's stable node id
let anc = el.parentElement;
let anchor = null;
while (anc) {
const nid = (anc.getAttribute && anc.getAttribute('data-xuda-node-id')) || func.runtime.ui.get_data(anc)?.xuData?.nodeid;
if (nid) {
anchor = nid;
break;
}
if (anc === root) break;
anc = anc.parentElement;
}
rescued.push({ node: el, anchor, reason: candidates[i].reason });
}
if (rescued.length) {
// suppress the xu-ref observer for our own removeChild moves (cleared on the next macrotask,
// after the observer microtask has flushed)
func.runtime.ui.__xu_preserve_suppress = true;
setTimeout(function () {
func.runtime.ui.__xu_preserve_suppress = false;
}, 0);
}
for (let i = 0; i < rescued.length; i++) {
const n = rescued[i].node;
if (n.parentNode) n.parentNode.removeChild(n);
}
if (rescued.length) {
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] preserve_detach ' +
JSON.stringify({
count: rescued.length,
nodes: rescued.map((r) => ({
id: (r.node && r.node.id) || '',
tag: r.node?.tagName || '',
anchor: r.anchor || null,
reason: r.reason || '',
})),
}),
);
} catch (e) {}
}
} catch (e) {
/* best-effort; never block the refresh */
}
return rescued;
};
func.runtime.ui.reattach_preserved_children = function ($container, rescued) {
if (!rescued || !rescued.length) return;
const attempt = function () {
let pending = 0;
// suppress the xu-ref observer for our own appendChild moves so re-attaching the preserved node does
// NOT re-enter refresh_screen (that would loop: reattach -> observer -> refresh -> re-render -> reattach).
// Cleared on the next macrotask, after the observer microtask has flushed.
func.runtime.ui.__xu_preserve_suppress = true;
setTimeout(function () {
func.runtime.ui.__xu_preserve_suppress = false;
}, 0);
try {
const root = func.runtime.ui.get_first_node($container);
if (!root) return rescued.length; // container not re-rendered yet — retry
let attached = 0;
for (let i = 0; i < rescued.length; i++) {
const r = rescued[i];
if (!r.node || r.node.isConnected) continue; // already re-attached
let target = null;
if (r.anchor && root.querySelector) {
target = root.querySelector(`[data-xuda-node-id="${r.anchor}"]`);
if (!target && func.runtime.ui.get_data(root)?.xuData?.nodeid === r.anchor) target = root;
}
if (!target) target = root; // fallback: the container root
if (!target.contains(r.node)) {
target.appendChild(r.node);
attached++;
try {
r.node.dispatchEvent(
new CustomEvent('xuda:preserved-reattach', {
bubbles: true,
detail: { anchor: r.anchor || null, reason: r.reason || '' },
}),
);
} catch (e) {}
}
}
try {
if (attached && root.dispatchEvent) {
root.dispatchEvent(new CustomEvent('xuda:preserved-children-reattached', { detail: { count: attached } }));
}
} catch (e) {}
} catch (e) {
/* best-effort */
}
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] preserve_reattach ' +
JSON.stringify({
pending,
nodes: rescued.map((r) => ({
id: (r.node && r.node.id) || '',
connected: !!r.node?.isConnected,
anchor: r.anchor || null,
reason: r.reason || '',
})),
}),
);
} catch (e) {}
return pending;
};
// The panel may render synchronously or defer/background-render its children, so the target
// container can appear a tick later — retry on the next tick(s) for any still-pending node.
if (attempt() > 0) {
setTimeout(attempt, 0);
setTimeout(attempt, 60);
setTimeout(attempt, 250);
}
};
// ---- same-frame datasource refresh coalescing ----
// A single user gesture can update a chain of dependent fields on the same datasource, producing a
// burst of refresh_screen calls before the browser has painted. Coalesce those same-datasource calls
// into one render carrying the union of changed fields. Distinct datasources and later-frame
// refreshes are untouched, and non-data refreshes (watcher/full-screen, which carry no
// fields_changed_arr) bypass coalescing entirely.
func.runtime.ui._refresh_coalesce = func.runtime.ui._refresh_coalesce || { buckets: new Map(), scheduled: false };
func.runtime.ui.refresh_screen = function (options) {
const _state = func.runtime.ui._refresh_coalesce;
const _ds_key = options?.fields_changed_datasource;
const _can_coalesce = typeof requestAnimationFrame === 'function' && _ds_key != null && Array.isArray(options?.fields_changed_arr);
if (!_can_coalesce) {
return func.runtime.ui._refresh_screen_impl(options);
}
let _bucket = _state.buckets.get(_ds_key);
if (!_bucket) {
_bucket = { options: options, fields: new Set(), waiters: [] };
_state.buckets.set(_ds_key, _bucket);
}
_bucket.options = options; // latest options win for this datasource; fields are unioned below
for (const _f of options.fields_changed_arr) _bucket.fields.add(_f);
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] refresh_screen_queue ' +
JSON.stringify({
datasource: _ds_key,
fields: Array.from(_bucket.fields),
waiters: _bucket.waiters.length + 1,
}),
);
} catch (e) {}
return new Promise(function (resolve) {
_bucket.waiters.push(resolve);
if (_state.scheduled) return;
_state.scheduled = true;
requestAnimationFrame(async function () {
_state.scheduled = false;
const _entries = Array.from(_state.buckets.entries());
_state.buckets.clear();
for (const [, _b] of _entries) {
const _merged = Object.assign({}, _b.options, { fields_changed_arr: Array.from(_b.fields) });
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] refresh_screen_flush ' +
JSON.stringify({
datasource: _merged.fields_changed_datasource,
fields: _merged.fields_changed_arr,
waiters: _b.waiters.length,
}),
);
} catch (e) {}
let _result;
try {
_result = await func.runtime.ui._refresh_screen_impl(_merged);
} catch (error) {
console.error(error);
}
for (const _w of _b.waiters) {
try {
_w(_result);
} catch (e) {}
}
}
});
});
};
func.runtime.ui._refresh_screen_impl = async function (options) {
const perf_end = func.runtime?.perf?.start?.(options.SESSION_ID, 'refresh_screen');
let found;
let refresh_reason;
let refresh_details;
try {
const validate_change = function (prog_doc, panelXuAttributes, skip_ui_check) {
found = null;
refresh_reason = null;
refresh_details = null;
if (options.watcher?.path?.includes('progDataSource')) {
found = true;
refresh_reason = `progDataSource by watcher ${options.watcher.path}`;
refresh_details = options.watcher;
return;
}
const refresh_dependencies = func.runtime.ui.get_prog_doc_refresh_dependencies(prog_doc);
const _attributes = panelXuAttributes || {};
const panel_refresh_dependencies = func.runtime.ui.get_panel_refresh_dependencies(_attributes);
const fields_to_validate = (options.fields_changed_arr || []).filter(function (field_id) {
return field_id !== 'SYS_GLOBAL_OBJ_REFS' || options.fields_changed_arr.length === 1;
});
for (const field_id of fields_to_validate) {
found = panel_refresh_dependencies.program_fields.has(field_id);
if (found) {
refresh_reason = `program ${_attributes['xu-exp:program']} ${field_id} changed `;
refresh_details = _attributes;
break;
}
for (const parameter_entry of panel_refresh_dependencies.parameter_entries) {
const parameter_in_field_id = parameter_entry.parameter_in_field_id;
if (parameter_entry.value_str?.includes(field_id)) {
found = refresh_dependencies.prog_fields.has(parameter_in_field_id);
if (found) {
refresh_reason = `field ${field_id} in progDataSource parameter_in changed`;
refresh_details = prog_doc?.progDataSource;
break;
}
if (!skip_ui_check) {
found = refresh_dependencies.xu_for_fields.has(parameter_in_field_id);
if (found) {
refresh_reason = `field ${field_id} in progUi xu-for parameter_in changed`;
refresh_details = found;
break;
}
}
}
}
if (found) break;
found = refresh_dependencies.prog_fields.has(field_id);
if (found) {
refresh_reason = `field ${field_id} in progDataSource changed`;
refresh_details = prog_doc?.progDataSource;
break;
}
if (!skip_ui_check) {
found = refresh_dependencies.xu_for_fields.has(field_id);
if (found) {
refresh_reason = `field ${field_id} in progUi xu-for changed`;
refresh_details = found;
break;
}
}
if (found) {
break;
}
}
};
if (options.fields_changed_datasource) {
const _session = SESSION_OBJ[options.SESSION_ID];
const _ds = _session.DS_GLB[options.fields_changed_datasource];
const prog_doc = await func.utils.DOCS_OBJ.get(options.SESSION_ID, _ds.prog_id);
if (prog_doc.progUi) {
validate_change(prog_doc, null, true);
if (found) {
const $elm = func.runtime.ui.find_in_root(options.SESSION_ID, `#container_${_ds.prog_id}_0`);
if ($elm?.length) {
const elm_data = func.runtime.ui.get_data($elm);
const refreshed_ds = _ds.dsSession;
// build-400 parity: preserve program-injected content across the main-program wipe
const _rescued_main = func.runtime.ui.detach_preserved_children($elm);
func.runtime.ui.empty($elm);
if (func.runtime.ui.get_data($elm)) {
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] refresh_screen_main_rerender ' +
JSON.stringify({
datasource: options.fields_changed_datasource || options.datasource_changed || null,
fields: options.fields_changed_arr || null,
reason: refresh_reason || null,
}),
);
} catch (e) {}
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_screen_main_rerenders');
await func.runtime.render.render_ui_tree(
options.SESSION_ID,
$elm,
structuredClone(elm_data.xuData.node),
{},
elm_data.xuData.paramsP,
null,
null,
elm_data.xuData.key,
refreshed_ds,
elm_data.xuData.parent_node,
null,
elm_data.xuData.$root_container,
);
func.runtime.ui.reattach_preserved_children($elm, _rescued_main);
if (glb.DEBUG_MODE) {
console.info('========= refresh main info ==============');
console.info('reason:', refresh_reason);
console.info('element:', $elm);
console.info('==========================================');
}
return;
}
}
}
}
}
const panels_obj = await func.UI.utils.get_panels_wrapper_from_dom(options.SESSION_ID, func.runtime.ui.get_root_element(options.SESSION_ID), false);
found = false;
const panel_ids = Object.keys(panels_obj || {});
for (let panel_index = 0; panel_index < panel_ids.length; panel_index++) {
found = false;
refresh_reason = null;
refresh_details = null;
const panel_val = panels_obj[panel_ids[panel_index]];
const panel_data = func.runtime.ui.get_data(panel_val.$panel_div);
const panel_xu_data = panel_data?.xuData;
if (!panel_xu_data) continue;
if (panel_xu_data.pending_to_delete) continue;
if (!options.watcher && options.fields_changed_arr) {
if (options.fields_changed_datasource && panel_val._ds.dsSession < Number(options.fields_changed_datasource)) {
continue;
}
validate_change(panel_val.prog_doc, panel_val?.panelXuAttributes);
}
if (options.datasource_changed && panel_val._ds.dsSession == options.datasource_changed) {
refresh_reason = `panel datasource ${options.datasource_changed} changed`;
refresh_details = '';
found = true;
}
if (found) {
// No-op refresh suppression (screen level): the changed datasource has ALREADY re-run
// by the time this notification arrives (its pre-refresh row snapshot is still attached).
// If the new rows are identical, skip this panel's teardown+re-render — that wipe/rebuild
// was the visible flick and multi-second freeze for zero data change. Genuinely changed
// rows leave the snapshot in place and re-render exactly as before.
try {
const _flick_ds = SESSION_OBJ[options.SESSION_ID]?.DS_GLB?.[panel_val._ds.dsSession];
const _flick_prev = _flick_ds && _flick_ds.__refresh_prev_rows;
if (options.datasource_changed && Array.isArray(_flick_prev)) {
const _flick_cur = (_flick_ds.data_feed && _flick_ds.data_feed.rows) || [];
const _flick_same =
_flick_prev.length === _flick_cur.length &&
_flick_cur.length > 0 &&
_flick_cur.every(function (row, row_index) {
const prev = _flick_prev[row_index];
return prev && prev._ROWID === row._ROWID && xu_isEqual(prev, row);
});
if (_flick_same) {
delete _flick_ds.__refresh_prev_rows;
console.log('%c[xuda-runtime] FLICK-SUPPRESSED — datasource ' + options.datasource_changed + ' re-ran with identical rows; panel re-render skipped', 'color:#16a34a;font-weight:bold');
continue;
}
}
} catch (e) {}
UI_WORKER_OBJ.cache = {};
const _session = SESSION_OBJ[options.SESSION_ID];
if (!_session) continue;
const $div_elm = panel_val.$panel_div;
const wrapper_data = panel_data;
const panel_node = func.runtime.ui.get_first_node($div_elm);
if (!panel_node?.isConnected) continue;
if (xu_isEmpty(wrapper_data)) continue;
const panel_refresh_key = [
options.datasource_changed || options.fields_changed_datasource || 'fields',
func.runtime.ui.get_attr($div_elm, 'xu-ui-id') || panel_ids[panel_index],
].join(':');
_session.panel_refresh_locks = _session.panel_refresh_locks || {};
const active_refresh = _session.panel_refresh_locks[panel_refresh_key];
if (active_refresh) {
active_refresh.pending = true;
continue;
}
const refresh_lock = { pending: false };
_session.panel_refresh_locks[panel_refresh_key] = refresh_lock;
let rerun_refresh = false;
if (glb.DEBUG_MODE) {
console.info('========= refresh info ==============');
console.info('reason:', refresh_reason);
console.info('details:', refresh_details);
console.info('panel:', panel_val);
console.info('=====================================');
}
try {
try {
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] refresh_screen_panel_rerender ' +
JSON.stringify({
datasource: options.fields_changed_datasource || options.datasource_changed || null,
fields: options.fields_changed_arr || null,
panel_datasource: panel_val?._ds?.dsSession || null,
panel_prog_id: panel_val?._ds?.prog_id || null,
panel_ui_id: func.runtime.ui.get_attr($div_elm, 'xu-ui-id') || panel_ids[panel_index] || null,
reason: refresh_reason || null,
}),
);
} catch (e) {}
func.runtime?.perf?.increment?.(options.SESSION_ID, 'refresh_screen_panel_rerenders');
// build-400 parity: detach program-injected content so the rebuild below doesn't destroy it
const rescued_foreign = func.runtime.ui.detach_preserved_children($div_elm);
// Keep the current content painted through the rebuild. Wipe-then-async-render left the
// panel EMPTY for the entire rebuild — a multi-second blank flash on every datasource
// change (the screen/modal "flick"). The old children stay in place (marked so refresh
// scans skip them); the rebuild's panel reconcile replaces same-elem_key panels in
// their slots, and whatever old content the rebuild did not claim is swept afterwards.
const existing_children = func.runtime.ui.get_children($div_elm);
const stale_children_nodes = [];
for (let child_index = 0; child_index < existing_children.length; child_index++) {
func.runtime.ui.mark_refresh_pending_delete?.(existing_children[child_index]);
const stale_node = func.runtime.ui.get_first_node(existing_children[child_index]);
if (stale_node) {
stale_children_nodes.push(stale_node);
}
}
func.runtime.ui.mark_refresh_index_dirty?.(options.SESSION_ID);
const had_skip_panel_replacement = Object.prototype.hasOwnProperty.call(wrapper_data, 'xuSkipPanelReplacement');
const previous_skip_panel_replacement = wrapper_data.xuSkipPanelReplacement;
wrapper_data.xuSkipPanelReplacement = true;
let refreshed_ds;
if (_session.DS_GLB[panel_val._ds.dsSession]) {
refreshed_ds = panel_val._ds.dsSession;
}
try {
for (let child_index = 0; child_index < wrapper_data.xuData.node_org.children.length; child_index++) {
const item = wrapper_data.xuData.node_org.children[child_index];
if (item.tagName !== 'xu-panel') continue;
await func.runtime.render.render_ui_tree(
options.SESSION_ID,
$div_elm,
structuredClone(item),
{},
wrapper_data.xuData.paramsP,
null,
null,
wrapper_data.xuData.key,
refreshed_ds,
wrapper_data.xuData.parent_node,
null,
wrapper_data.xuData.$root_container,
);
}
} finally {
if (had_skip_panel_replacement) {
wrapper_data.xuSkipPanelReplacement = previous_skip_panel_replacement;
} else {
delete wrapper_data.xuSkipPanelReplacement;
}
}
// Sweep old content the rebuild did not replace in place (removed rows, key drift).
// Runs only after the new tree has landed, so the panel is never blank in between;
// if the rebuild threw, the sweep is skipped and the old content stays visible.
for (let stale_index = 0; stale_index < stale_children_nodes.length; stale_index++) {
const stale_node = stale_children_nodes[stale_index];
if (stale_node.isConnected) {
func.runtime.ui.remove(stale_node);
}
}
func.runtime.ui.mark_refresh_index_dirty?.(options.SESSION_ID);
// build-400 parity: re-attach the rescued program-injected content into the re-rendered panel
func.runtime.ui.reattach_preserved_children($div_elm, rescued_foreign);
} catch (error) {
console.error('[xuda-runtime] caught xuda_runtime.browser.refresh.js:314:', error);
}
} finally {
rerun_refresh = !!refresh_lock.pending;
if (_session.panel_refresh_locks?.[panel_refresh_key] === refresh_lock) {
delete _session.panel_refresh_locks[panel_refresh_key];
}
}
if (rerun_refresh) {
setTimeout(function () {
func.runtime.ui._refresh_screen_impl(options).catch(async function (error) {
if (func.utils?.report_issue) {
await func.utils.report_issue(options.SESSION_ID, {
code: 'RUN_MSG_RND_071',
source: 'refresh_screen',
message: 'queued panel refresh failed',
type: 'W',
err: error,
details: {
datasource_changed: options.datasource_changed,
fields_changed_datasource: options.fields_changed_datasource,
panel_refresh_key,
},
});
}
});
}, 0);
}
}
}
} finally {
perf_end?.();
}
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
// Browser-only realtime refresh helpers live here so refresh orchestration can stay focused.
func.runtime.ui.refresh_document_changes_for_realtime_update = async function (SESSION_ID, doc_change) {
const _session = SESSION_OBJ[SESSION_ID];
for (const [key, _ds] of Object.entries(_session.DS_GLB)) {
const prog_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
if (prog_obj?.progDataSource?.dataSourceRealtime && prog_obj?.progDataSource?.dataSourceTableId === doc_change.table_id) {
try {
if (!_ds.screen_params) continue;
if (_ds.screen_params.is_panelP) {
await func.runtime.ui.refresh_screen({ SESSION_ID, fields_changed_arr: null, datasource_changed: key });
} else {
await func.action.execute(SESSION_ID, 'act_refresh', _ds, null, null);
}
} catch (err) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_RND_070',
source: 'refresh_document_changes_for_realtime_update',
message: 'realtime refresh failed',
type: 'W',
err,
details: {
datasource: key,
table_id: doc_change.table_id,
prog_id: _ds.prog_id,
},
});
}
}
}
if (glb.new_xu_render) {
for (const [ui_cache_key, ui_cache_val] of Object.entries(UI_WORKER_OBJ.xu_render_cache)) {
const prog_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, ui_cache_val.paramsP.prog_id);
if (prog_obj?.progDataSource?.dataSourceTableId === doc_change.table_id) {
ui_cache_val.$div = null;
}
}
}
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only render draw helpers live here so the browser runtime can be split by concern.
func.runtime.render.get_screen_context = function (SESSION_ID, $container, paramsP, is_skeleton) {
const _session = SESSION_OBJ[SESSION_ID];
const _ds = is_skeleton ? null : _session?.DS_GLB?.[paramsP.dsSessionP];
const container_xu_data = func.runtime.ui.get_data($container)?.xuData;
let currentRecordId = container_xu_data?.recordid || _ds?.currentRecordId || '';
// Same sentinel rule as create_container — keeps element matching consistent with the
// upgraded record ids stamped on children (see xuda_runtime.browser.dom.js).
if (currentRecordId === 'newRecord' && _ds?.currentRecordId && _ds.currentRecordId !== 'newRecord') {
const _live_rows = _ds.data_feed?.rows;
if (Array.isArray(_live_rows) && _live_rows.some((row) => row._ROWID === _ds.currentRecordId)) {
currentRecordId = _ds.currentRecordId;
}
}
return {
_session,
_ds,
container_xu_data,
currentRecordId,
is_mobile: glb.MOBILE_ARR.includes(paramsP.screenInfo.properties?.menuType),
};
};
func.runtime.render.get_node_attributes = function (nodeP) {
try {
if (func.runtime.render.is_tree_node?.(nodeP)) {
return nodeP.attributes;
}
return nodeP?.attributes;
} catch (error) {
return undefined;
}
};
func.runtime.render.find_existing_element = function (options) {
const currentRecordId = options.currentRecordId || options.render_context?.currentRecordId;
const SESSION_ID = options.SESSION_ID || func.runtime.ui.get_data(options.$container)?.xuData?.SESSION_ID;
const is_matching_candidate = function (candidate) {
if (!candidate) return false;
const candidate_data = func.runtime.ui.get_data(candidate);
return (
!candidate_data?.xuData?.is_placeholder &&
!candidate_data?.xuData?.xu_for_placeholder &&
candidate_data?.xuData?.recordid === currentRecordId &&
candidate_data?.xuData?.key === options.keyP &&
candidate.tagName !== 'XURENDER'
);
};
const get_indexed_match = function () {
if (!SESSION_ID || !func.runtime.ui.get_refresh_indexed_elements_by_node_id) {
return null;
}
const indexed_candidates = func.runtime.ui.get_refresh_indexed_elements_by_node_id(SESSION_ID, options.nodeP.id).toArray();
for (let index = 0; index < indexed_candidates.length; index++) {
const candidate = indexed_candidates[index];
if (candidate.parentElement !== func.runtime.ui.get_first_node(options.$container)) {
continue;
}
if (is_matching_candidate(candidate)) {
return func.runtime.ui._wrap_matches([candidate]);
}
}
return null;
};
const indexed_match = get_indexed_match();
if (indexed_match?.length) {
return {
div: indexed_match,
candidates: indexed_match,
};
}
let $candidates = func.runtime.ui.find_element_data_in_parent(options.$container, 'xuData', 'nodeid', options.nodeP.id);
let $matched = null;
const fallback_candidates = $candidates?.toArray?.() || [];
for (let index = 0; index < fallback_candidates.length; index++) {
const candidate = fallback_candidates[index];
if (is_matching_candidate(candidate)) {
$matched = func.runtime.ui._wrap_matches([candidate]);
break;
}
}
return {
div: $matched,
candidates: $candidates,
};
};
func.runtime.render.log_tree_debug = function (options) {
return func.utils.debug.log(options.SESSION_ID, options.paramsP.prog_id + '_' + options.nodeP.id_org + '_ui_prop', {
module: 'gui',
action: 'init',
prop: options.nodeP.id,
details: options.error_descP,
result: null,
error: options.is_errorP,
source: options._ds?.tree_obj?.menuName || '',
fields: null,
type: null,
prog_id: options.paramsP.prog_id,
dsSession: null,
});
};
func.runtime.render.create_temp_render_container = function ($container) {
const tmp = document.createElement('tmp');
func.runtime.ui.set_data(tmp, 'xuData', func.runtime.ui.get_data($container)?.xuData);
return tmp;
};
func.runtime.render.insert_ordered_child = function ($container, $child, target_key) {
const children = func.runtime.ui.get_children($container);
if (!children.length) {
return func.runtime.ui.append_to($child, $container);
}
const normalized_target_key = Number.isFinite(Number(target_key)) ? Number(target_key) : `${target_key}`;
for (let index = 0; index < children.length; index++) {
const existing_child = children[index];
const existing_key = func.runtime.ui.get_data(existing_child)?.xuData?.key;
if (typeof existing_key === 'undefined' || existing_key === null) {
continue;
}
const normalized_existing_key = Number.isFinite(Number(existing_key)) ? Number(existing_key) : `${existing_key}`;
if (normalized_existing_key > normalized_target_key) {
return func.runtime.ui.insert_before($child, existing_child);
}
}
return func.runtime.ui.append_to($child, $container);
};
func.runtime.render.prepare_draw_context = async function (options) {
let temp_$div = null;
let render_container = options.$container;
let $div;
if (options.buffered) {
temp_$div = await func.runtime.ui.create_container({
SESSION_ID: options.SESSION_ID,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
treeP: options.treeP,
parent_nodeP: options.parent_nodeP,
prop: options.prop,
div_typeP: options.element,
attr_str: options.attr_str || '',
is_placeholder: true,
});
render_container = func.runtime.render.create_temp_render_container(options.$container);
$div = func.runtime.ui.get_first_node(temp_$div)?.cloneNode(true);
const cloned_data = func.runtime.ui.get_data($div);
if (cloned_data?.xuData) {
cloned_data.xuData.is_placeholder = false;
}
} else {
$div = await func.runtime.ui.create_container({
SESSION_ID: options.SESSION_ID,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
treeP: options.treeP,
parent_nodeP: options.parent_nodeP,
prop: options.prop,
div_typeP: options.element,
attr_str: options.attr_str || '',
});
}
return {
$div,
temp_$div,
$live_elm: temp_$div || $div,
render_container,
};
};
func.runtime.render.run_draw_pipeline = async function (options) {
if (!options.element || options.element === 'script') {
return {
$div: null,
temp_$div: null,
render_container: options.$container,
ret: {},
};
}
const draw_context = await func.runtime.render.prepare_draw_context(options);
const { $div, temp_$div, $live_elm, render_container } = draw_context;
func.runtime.render.bind_hover_handlers($div, options.hover_handlers, options.include_hover_click);
const ret = await func.runtime.render.set_attributes_new({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: render_container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
$elm: $div,
$live_elm,
is_init: true,
});
if (!func.runtime.render.should_stop_after_attributes(options.nodeP, ret)) {
await func.runtime.render.process_post_attribute_children({
SESSION_ID: options.SESSION_ID,
$div,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
$root_container: options.$root_container,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
is_skeleton: options.is_skeleton,
keyP: options.keyP,
refreshed_ds: options.refreshed_ds,
parent_nodeP: options.parent_nodeP,
check_existP: options.check_existP,
render_container,
hover_in: options.hover_handlers.hover_in,
iterate_child: options.iterate_child,
await_children: !options.buffered,
defer_when_background: options.buffered,
ret,
});
}
return {
$div,
temp_$div,
$live_elm,
render_container,
ret,
};
};
// Browser render draw helpers remain here. Xu-render cache helpers moved to xuda_runtime.browser.render.cache.js.
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
func.runtime.render.parent_ds_fields_cache = func.runtime.render.parent_ds_fields_cache || new Map();
func.runtime.render.parent_ds_field_names_cache = func.runtime.render.parent_ds_field_names_cache || new Map();
func.runtime.render.xu_render_cache_str_cache = func.runtime.render.xu_render_cache_str_cache || new Map();
func.runtime.render.xu_render_exclude_fields_cache = func.runtime.render.xu_render_exclude_fields_cache || new WeakMap();
// Browser-only xu-render cache helpers live here so the draw pipeline can stay focused.
func.runtime.render.set_small_cache_entry = function (cache, key, value, max_entries = 1000) {
if (cache.size >= max_entries && !cache.has(key)) {
cache.clear();
}
cache.set(key, value);
return value;
};
func.runtime.render.get_runtime_descendants = function ($div) {
const _div_node = func.runtime.ui.get_first_node($div);
if (!_div_node) {
return [];
}
return _div_node.querySelectorAll?.('[xu-ui-id]') || [];
};
func.runtime.render.collect_dependency_fields_from_store = function (dependency_store, dependency_fields) {
const attr_keys = Object.keys(dependency_store || {});
for (let attr_index = 0; attr_index < attr_keys.length; attr_index++) {
const fields = dependency_store[attr_keys[attr_index]] || [];
for (let field_index = 0; field_index < fields.length; field_index++) {
dependency_fields.add(fields[field_index]);
}
}
return dependency_fields;
};
func.runtime.render.collect_dependency_fields_from_attributes = function (xu_attributes, dependency_fields) {
const attr_keys = Object.keys(xu_attributes || {});
for (let attr_index = 0; attr_index < attr_keys.length; attr_index++) {
const attr_val = xu_attributes[attr_keys[attr_index]];
if (typeof attr_val !== 'string') {
continue;
}
const matches = attr_val.match(/@([A-Za-z0-9_]+)/g) || [];
for (let match_index = 0; match_index < matches.length; match_index++) {
dependency_fields.add(matches[match_index].slice(1));
}
}
return dependency_fields;
};
func.runtime.render.build_xu_render_original_data = function (options) {
return {
$container: options.$container,
nodeP: func.runtime.ui.get_node_snapshot ? func.runtime.ui.get_node_snapshot(options.nodeP) : structuredClone(options.nodeP),
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
$root_container: options.$root_container,
};
};
func.runtime.render.attach_xu_render_state = function ($xurender, options) {
func.runtime.ui.set_data($xurender, 'xuData', options.xuData);
func.runtime.ui.get_data($xurender).xuData.original_data_obj = options.original_data_obj;
if (options.xurender_node) {
func.runtime.ui.get_data($xurender).xuData.xurender_node = options.xurender_node;
}
func.runtime.ui.get_data($xurender).xuAttributes = options.xuAttributes || {};
return $xurender;
};
func.runtime.render.create_xu_render_placeholder = function (xu_ui_id, $target, options = {}) {
const $xurender = func.runtime.ui.create_xurender(xu_ui_id, $target, options.hidden);
return func.runtime.render.attach_xu_render_state($xurender, options);
};
func.runtime.render.collect_dependency_fields = function ($div) {
if (!$div?.length) {
return [];
}
const dependency_fields = new Set();
const descendants = func.runtime.render.get_runtime_descendants($div);
for (let index = 0; index < descendants.length; index++) {
const elm_data = func.runtime.ui.get_data(descendants[index]);
const dependency_store = elm_data?.xuData?.refresh_dependency_by_attr;
if (dependency_store) {
func.runtime.render.collect_dependency_fields_from_store(dependency_store, dependency_fields);
continue;
}
const xu_attributes = elm_data?.xuAttributes;
if (!xu_attributes) {
continue;
}
func.runtime.render.collect_dependency_fields_from_attributes(xu_attributes, dependency_fields);
}
return [...dependency_fields];
};
func.runtime.render.cache_xu_render = function (cache_key, value) {
if (value?.$div && !value.dependency_fields) {
value.dependency_fields = func.runtime.render.collect_dependency_fields(value.$div);
}
UI_WORKER_OBJ.xu_render_cache[cache_key] = value;
return UI_WORKER_OBJ.xu_render_cache[cache_key];
};
func.runtime.render.get_parent_ds_cache_signature = function (SESSION_ID, dsSessionP) {
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSessionP];
if (!_ds) {
return `${dsSessionP || ''}:`;
}
let signature = `${dsSessionP}:${_ds.currentRecordId || ''}`;
if (typeof _ds.parentDataSourceNo !== 'undefined') {
signature += `|${func.runtime.render.get_parent_ds_cache_signature(SESSION_ID, _ds.parentDataSourceNo)}`;
}
return signature;
};
func.runtime.render.build_parent_ds_fields = function (SESSION_ID, dsSessionP) {
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSessionP];
if (!_ds) {
return {};
}
const idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
const data = _ds.data_feed.rows[idx] || {};
let obj = {};
if (typeof _ds.parentDataSourceNo !== 'undefined') {
obj = func.runtime.render.get_parent_ds_fields(SESSION_ID, _ds.parentDataSourceNo);
}
return { ...data, ...obj };
};
func.runtime.render.get_parent_ds_fields = function (SESSION_ID, dsSessionP) {
const signature = func.runtime.render.get_parent_ds_cache_signature(SESSION_ID, dsSessionP);
const cache_key = `${SESSION_ID}:${signature}`;
if (func.runtime.render.parent_ds_fields_cache.has(cache_key)) {
return func.runtime.render.parent_ds_fields_cache.get(cache_key);
}
const fields = func.runtime.render.build_parent_ds_fields(SESSION_ID, dsSessionP);
return func.runtime.render.set_small_cache_entry(func.runtime.render.parent_ds_fields_cache, cache_key, fields);
};
func.runtime.render.get_parent_ds_field_names = function (SESSION_ID, dsSessionP) {
const signature = func.runtime.render.get_parent_ds_cache_signature(SESSION_ID, dsSessionP);
const cache_key = `${SESSION_ID}:${signature}`;
if (func.runtime.render.parent_ds_field_names_cache.has(cache_key)) {
return func.runtime.render.parent_ds_field_names_cache.get(cache_key);
}
const fields = Object.keys(func.runtime.render.get_parent_ds_fields(SESSION_ID, dsSessionP));
return func.runtime.render.set_small_cache_entry(func.runtime.render.parent_ds_field_names_cache, cache_key, fields);
};
func.runtime.render.get_xu_render_exclude_fields = function ($elm) {
const fields_obj = func.runtime.ui.get_data($elm)?.xuData?.attr_exp_info?.['xu-render']?.fields;
if (!fields_obj) {
return [];
}
if (func.runtime.render.xu_render_exclude_fields_cache.has(fields_obj)) {
return func.runtime.render.xu_render_exclude_fields_cache.get(fields_obj);
}
const fields = Object.keys(fields_obj);
func.runtime.render.xu_render_exclude_fields_cache.set(fields_obj, fields);
return fields;
};
func.runtime.render.get_xu_render_cache_str = async function (SESSION_ID, dsSessionP, exclude_vars = []) {
const exclude_key = [...exclude_vars].sort().join('|');
const signature = func.runtime.render.get_parent_ds_cache_signature(SESSION_ID, dsSessionP);
const cache_key = `${SESSION_ID}:${signature}:${exclude_key}`;
if (func.runtime.render.xu_render_cache_str_cache.has(cache_key)) {
return func.runtime.render.xu_render_cache_str_cache.get(cache_key);
}
const fields_obj = func.runtime.render.get_parent_ds_fields(SESSION_ID, dsSessionP);
const exclude_vars_set = new Set(exclude_vars);
let str = '';
const field_keys = Object.keys(fields_obj);
for (let index = 0; index < field_keys.length; index++) {
const key = field_keys[index];
const val = fields_obj[key];
if (exclude_vars_set.has(key)) continue;
str += typeof val !== 'undefined' ? JSON.stringify(val) : '';
}
const cache_str = 'C-' + (await func.common.sha256(str));
return func.runtime.render.set_small_cache_entry(func.runtime.render.xu_render_cache_str_cache, cache_key, cache_str);
};
func.runtime.render.has_parent_field_dependency = function ($div, parent_fields = [], dependency_fields = null) {
if (!parent_fields?.length) {
return false;
}
const parent_field_set = new Set(parent_fields);
if (dependency_fields?.length) {
for (let index = 0; index < dependency_fields.length; index++) {
if (parent_field_set.has(dependency_fields[index])) {
return true;
}
}
return false;
}
if (!$div?.length) {
return false;
}
const descendants = func.runtime.render.get_runtime_descendants($div);
for (let index = 0; index < descendants.length; index++) {
const elm_data = func.runtime.ui.get_data(descendants[index]);
const dependency_store = elm_data?.xuData?.refresh_dependency_by_attr;
if (dependency_store) {
const attr_keys = Object.keys(dependency_store);
for (let attr_index = 0; attr_index < attr_keys.length; attr_index++) {
const fields = dependency_store[attr_keys[attr_index]] || [];
for (let field_index = 0; field_index < fields.length; field_index++) {
if (parent_field_set.has(fields[field_index])) {
return true;
}
}
}
continue;
}
const xu_attributes = elm_data?.xuAttributes;
if (!xu_attributes) {
continue;
}
const attr_keys = Object.keys(xu_attributes);
for (let attr_index = 0; attr_index < attr_keys.length; attr_index++) {
const attr_val = xu_attributes[attr_keys[attr_index]];
if (typeof attr_val !== 'string') {
continue;
}
for (let field_index = 0; field_index < parent_fields.length; field_index++) {
if (attr_val.includes('@' + parent_fields[field_index])) {
return true;
}
}
}
}
return false;
};
func.runtime.render.finalize_buffered_draw = async function (options) {
const xu_ui_id = func.runtime.ui.get_attr(options.$div, 'xu-ui-id');
if (options.ret.has_xu_exp_render_attribute) {
const exclude_fields = func.runtime.render.get_xu_render_exclude_fields(options.$div);
const xu_render_cache_id = await func.runtime.render.get_xu_render_cache_str(
options.SESSION_ID,
options.paramsP.dsSessionP,
exclude_fields,
);
const _$div = func.runtime.ui.get_first_node(options.$div)?.cloneNode(true);
func.runtime.render.cache_xu_render(xu_ui_id + xu_render_cache_id, { $div: _$div, paramsP: options.paramsP, data: func.runtime.ui.get_data(_$div) });
options.nodeP.xu_render_xu_ui_id = xu_ui_id;
options.nodeP.xu_render_cache_id = xu_render_cache_id;
}
return func.runtime.render.finalize_temp_draw(options.temp_$div, options.$div, options.ret);
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
func.runtime.render.viewport_observers = func.runtime.render.viewport_observers || null;
func.runtime.render.style_tag_cache = func.runtime.render.style_tag_cache || new Set();
// Browser-only post-render behavior helpers live here so the core render tree can stay focused.
func.runtime.render.finalize_temp_draw = function (temp_$div, $div, ret = {}) {
func.runtime.ui.remove_class($div, 'display_none');
if (ret.consume_placeholder) {
func.runtime.ui.remove(temp_$div);
return $div;
}
if (ret.xu_render_background_processing || ret.has_xu_render_attribute) {
func.runtime.ui.remove(temp_$div);
return $div;
}
func.runtime.ui.replace_with(temp_$div, $div);
return $div;
};
func.runtime.render.get_viewport_observers = function () {
if (func.runtime.render.viewport_observers) {
return func.runtime.render.viewport_observers;
}
const observer_inViewport = new IntersectionObserver(
function (entries) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.dispatchEvent(new CustomEvent('inViewport'));
observer_inViewport.unobserve(entry.target);
}
});
},
{
threshold: 0.1,
},
);
const observer_outViewport = new IntersectionObserver(
function (entries) {
entries.forEach((entry) => {
if (!entry.isIntersecting) {
entry.target.dispatchEvent(new CustomEvent('outViewport'));
}
});
},
{
threshold: 0,
},
);
func.runtime.render.viewport_observers = {
observer_inViewport,
observer_outViewport,
};
return func.runtime.render.viewport_observers;
};
func.runtime.render.bind_viewport_render = function ($div, handlers) {
const { observer_inViewport, observer_outViewport } = func.runtime.render.get_viewport_observers();
const div_node = func.runtime.ui.get_first_node($div);
let ui_job_id;
// Remove previous handlers if they exist, then attach new ones
if (div_node._xuda_inViewportHandler) {
div_node.removeEventListener('inViewport', div_node._xuda_inViewportHandler);
}
div_node._xuda_inViewportHandler = function () {
ui_job_id = handlers.onEnter({ observer_outViewport });
};
div_node.addEventListener('inViewport', div_node._xuda_inViewportHandler);
if (div_node._xuda_outViewportHandler) {
div_node.removeEventListener('outViewport', div_node._xuda_outViewportHandler);
}
div_node._xuda_outViewportHandler = function () {
handlers.onExit({ observer_inViewport, ui_job_id });
};
div_node.addEventListener('outViewport', div_node._xuda_outViewportHandler);
func.runtime.ui.add_class($div, 'skeleton');
observer_inViewport.observe(div_node);
return $div;
};
func.runtime.render.has_terminal_content_attributes = function (nodeP) {
return (
!xu_isEmpty(nodeP?.attributes?.['xu-text']) ||
!xu_isEmpty(nodeP?.attributes?.['xu-html']) ||
!xu_isEmpty(nodeP?.attributes?.['xu-exp:xu-text']) ||
!xu_isEmpty(nodeP?.attributes?.['xu-exp:xu-html'])
);
};
func.runtime.render.should_stop_after_attributes = function (nodeP, ret = {}) {
return !!(ret.abort || nodeP.tagName === 'svg' || func.runtime.render.has_terminal_content_attributes(nodeP));
};
func.runtime.render.should_use_viewport_render = function (nodeP) {
return nodeP?.attributes?.['xu-viewport'] == 'true';
};
func.runtime.render.bind_draw_viewport = function (options) {
const div_node = func.runtime.ui.get_first_node(options.$div);
func.runtime.render.bind_viewport_render(options.$div, {
onEnter: ({ observer_outViewport }) => {
if (div_node?.childElementCount) {
func.runtime.ui.remove_class(options.$div, 'skeleton');
return null;
}
options.hover_in(options.$div);
const ui_job_id = func.UI.worker.add_to_queue(
options.SESSION_ID,
'gui event',
'render_viewport',
{
$div: options.$div,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
$root_container: options.$root_container,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
is_skeleton: options.is_skeleton,
keyP: options.keyP,
refreshed_ds: options.refreshed_ds,
parent_nodeP: options.parent_nodeP,
check_existP: options.check_existP,
$container: options.render_container,
},
null,
null,
options.paramsP.dsSessionP,
);
observer_outViewport.observe(div_node);
return ui_job_id;
},
onExit: ({ observer_inViewport, ui_job_id }) => {
func.UI.worker.delete_job(options.SESSION_ID, ui_job_id);
if (div_node?.childElementCount) {
func.runtime.ui.empty(options.$div);
const height = func.runtime.ui.get_data(options.$div)?.xuData?.viewport_height || 10;
if (typeof height !== 'undefined') {
func.runtime.ui.set_style(options.$div, 'height', `${height}px`);
}
}
observer_inViewport.observe(div_node);
},
});
return options.$div;
};
func.runtime.render.process_post_attribute_children = async function (options) {
if (func.runtime.render.should_use_viewport_render(options.nodeP)) {
return func.runtime.render.bind_draw_viewport(options);
}
if (options.defer_when_background && options.ret?.xu_render_background_processing) {
return options.$div;
}
if (options.await_children) {
await options.iterate_child(options.$div, options.nodeP, options.parent_infoP, options.$root_container);
return options.$div;
}
options.iterate_child(options.$div, options.nodeP, options.parent_infoP, options.$root_container);
return options.$div;
};
func.runtime.render.append_style_tag = function (cssText) {
if (!cssText || func.runtime.render.style_tag_cache.has(cssText)) {
return false;
}
func.runtime.render.style_tag_cache.add(cssText);
const style_node = document.createElement('style');
style_node.textContent = cssText;
document.head.appendChild(style_node);
return true;
};
func.runtime.render.scope_css_to_xu_ui = function ($elm, cssText) {
var parser = new cssjs();
var parsed = parser.parseCSS(cssText);
var xuUiId = `[xu-ui-id="${func.runtime.ui.get_attr($elm, 'xu-ui-id')}"]`;
for (var key = 0; key < parsed.length; key++) {
var val = parsed[key];
var selectors_arr = val.selector.split(',');
for (var key2 = 0; key2 < selectors_arr.length; key2++) {
selectors_arr[key2] = `${xuUiId} ${selectors_arr[key2]}, ${xuUiId}${selectors_arr[key2]}`;
}
val.selector = selectors_arr.join(',');
}
return parser.getCSSForEditor(parsed);
};
func.runtime.render.bind_xu_event = function (options) {
const decode_html_entities = function (value) {
if (typeof value !== 'string' || value.indexOf('&') === -1) {
return value;
}
if (typeof document !== 'undefined') {
const textarea = document.createElement('textarea');
textarea.innerHTML = value;
return textarea.value;
}
return value
.replaceAll('"', '"')
.replaceAll('"', '"')
.replaceAll(''', "'")
.replaceAll(''', "'")
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('&', '&');
};
const normalize_event_handlers = function (raw_handlers) {
if (typeof raw_handlers !== 'string') {
return raw_handlers;
}
const decoded_value = decode_html_entities(raw_handlers).trim();
if (!decoded_value) {
return [];
}
try {
return JSON5.parse(decoded_value);
} catch (error) {
func.utils.report_issue(options.SESSION_ID, {
code: 'RUN_MSG_RND_010',
source: 'xu-on',
message: `xu-on has invalid workflow syntax: ${decoded_value}`,
type: 'E',
err: error,
details: {
trigger: options.val.key,
value: decoded_value,
},
});
return [];
}
};
CLIENT_ACTIVITY_TS = Date.now();
const trigger = options.val.key.split('xu-on:')[1].toLowerCase();
const handler_key = `_xuda_xuOn_${trigger.replace(/[^a-z0-9_]/gi, '_')}`;
const elm_node = func.runtime.ui.get_first_node(options.$elm);
if (elm_node[handler_key]) {
elm_node.removeEventListener(trigger, elm_node[handler_key]);
}
elm_node[handler_key] = async function (evt) {
const _$elm = evt.currentTarget;
const elm_data = func.runtime.ui.get_data(_$elm);
const xuAttributes = elm_data?.xuAttributes;
const event_attr_key = 'xu-on:' + evt.type;
const event_handlers = normalize_event_handlers(xuAttributes?.[event_attr_key]);
if (xuAttributes && event_handlers !== xuAttributes?.[event_attr_key]) {
xuAttributes[event_attr_key] = event_handlers;
}
if (xu_isEmpty(xuAttributes) || xu_isEmpty(event_handlers)) return;
const handler_keys = Object.keys(event_handlers);
for (let handler_index = 0; handler_index < handler_keys.length; handler_index++) {
const val = event_handlers[handler_keys[handler_index]];
const handler_props = val?.props || {};
if (!xu_isEmpty(handler_props.condition)) {
const expCond = await func.expression.get(options.SESSION_ID, handler_props.condition, options.paramsP.dsSessionP, 'condition', options.paramsP.recordid);
if (!expCond.result) continue;
}
if (val?.event_modifiers && evt[val.event_modifiers]) {
evt[val.event_modifiers]();
}
const workflow = val?.workflow || val?.event;
if (workflow) {
const workflow_keys = Object.keys(workflow);
for (let workflow_index = 0; workflow_index < workflow_keys.length; workflow_index++) {
const val2 = workflow[workflow_keys[workflow_index]];
if (!val2?.data?.action) continue;
if (val2.data.enabled === false) continue;
func.events.add_to_queue(
options.SESSION_ID,
'element event',
val2.id,
evt.type,
val2.data.action,
val2.data.name,
null,
func.runtime.ui.get_attr(_$elm, 'xu-ui-id'),
null,
evt,
null,
null,
null,
options.paramsP.dsSessionP,
null,
null,
null,
evt.type,
val2.data.name,
null,
null,
val2,
null,
null,
null,
null,
null,
null,
);
}
}
}
};
elm_node.addEventListener(trigger, elm_node[handler_key]);
return options.$elm;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only hover/debug state helpers live here so post-render behavior can stay focused on non-hover flow.
func.runtime.render.should_bind_hover_click = function (paramsP, parent_infoP) {
return paramsP.paramsP === 'grid' || !!parent_infoP?.iterate_info;
};
func.runtime.render.create_hover_handlers = function (options) {
return {
hover_in: function ($div, e) {
return func.runtime.render.handle_hover_in({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
e,
$div,
$container: options.$container,
_ds: options._ds,
paramsP: options.paramsP,
});
},
hover_out: function () {
return func.runtime.render.handle_hover_out({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$container: options.$container,
_ds: options._ds,
});
},
};
};
func.runtime.render.bind_hover_handlers = function ($div, handlers, include_click) {
const el = func.runtime.ui.get_first_node($div);
if (!el) return $div;
el.addEventListener('mouseenter', function (e) {
handlers.hover_in($div, e);
});
el.addEventListener('mouseleave', function () {
handlers.hover_out();
});
if (include_click) {
el.addEventListener('click', function (e) {
handlers.hover_in($div, e);
});
el.addEventListener('contextmenu', function (e) {
handlers.hover_in($div, e);
});
}
return $div;
};
func.runtime.render.resolve_debug_element = function ($elm) {
try {
const el = func.runtime.ui.get_first_node($elm);
const id = el?.getAttribute?.('xu-ui-id');
if (!id || !glb.DEBUG_MODE) {
return $elm;
}
const matches = document.querySelectorAll(`[xu-ui-id="${id}"]`);
if (matches.length > 1) {
console.warn('Multiple elements for xu-ui-id: ' + id, matches);
}
return matches[0] || $elm;
} catch (e) {
console.error(e);
return $elm;
}
};
func.runtime.render.set_hover_item = function ($container, $target) {
const resolved_container = func.runtime.render.resolve_debug_element($container);
const resolved_data = func.runtime.ui.get_data(resolved_container);
if (!resolved_data?.xuData?.debug_info) {
return false;
}
const target_el = func.runtime.ui.get_first_node($target);
resolved_data.xuData.debug_info.hover_item = target_el?.getAttribute?.('xu-ui-id') || null;
return true;
};
func.runtime.render.get_element_attributes = function ($div) {
let attributes = {};
const el = func.runtime.ui.get_first_node($div);
const attrs = el?.attributes || [];
for (let index = 0; index < attrs.length; index++) {
const attr = attrs[index];
attributes[attr.name] = attr.value;
}
return attributes;
};
func.runtime.render.set_hovered_attributes = function (SESSION_ID, attributes) {
const root_data_system = func.runtime.render.get_root_data_system(SESSION_ID);
if (!root_data_system) {
return false;
}
root_data_system.SYS_OBJ_WIN_ELEMENT_HOVERED_ATTRIBUTES = attributes;
return true;
};
func.runtime.render.clear_hovered_attributes = function (SESSION_ID) {
return func.runtime.render.set_hovered_attributes(SESSION_ID, {});
};
func.runtime.render.queue_datasource_update = function (SESSION_ID, dsSessionP, currentRecordId, field_id, field_value) {
const payload = {
currentRecordId,
};
if (typeof field_id !== 'undefined') {
payload.field_id = field_id;
payload.field_value = field_value;
}
func.UI.worker.add_to_queue(SESSION_ID, 'gui event', 'update_datasource', payload, null, null, dsSessionP);
return payload;
};
func.runtime.render.queue_hover_updates = function (options) {
const resolved_div = func.runtime.render.resolve_debug_element(options.$div);
const resolved_data = func.runtime.ui.get_data(resolved_div);
const currentRecordId = resolved_data?.xuData?.currentRecordId;
if (options.$div && resolved_div && options._ds && options.paramsP.renderType === 'grid' && currentRecordId) {
func.runtime.render.queue_datasource_update(options.SESSION_ID, options.paramsP.dsSessionP, currentRecordId);
}
const div_data = func.runtime.ui.get_data(options.$div);
const iterate_info = div_data?.xuData?.iterate_info || div_data?.iterate_info;
if (!iterate_info || !currentRecordId) {
return false;
}
if (iterate_info.iterator_key) {
func.runtime.render.queue_datasource_update(options.SESSION_ID, options.paramsP.dsSessionP, currentRecordId, iterate_info.iterator_key, iterate_info._key);
}
if (iterate_info.iterator_val) {
func.runtime.render.queue_datasource_update(options.SESSION_ID, options.paramsP.dsSessionP, currentRecordId, iterate_info.iterator_val, iterate_info._val);
}
return true;
};
func.runtime.render.handle_hover_in = function (options) {
if (options.is_skeleton || (options.e && (EXP_BUSY || UI_WORKER_OBJ.active_jobs_count))) {
return false;
}
CLIENT_ACTIVITY_TS = Date.now();
const $target = options.$div || options.$container;
func.runtime.render.set_hover_item(options.$container, $target);
if (!options._ds) {
return true;
}
func.runtime.render.set_hovered_attributes(options.SESSION_ID, func.runtime.render.get_element_attributes($target));
const target_data = func.runtime.ui.get_data($target);
if (!target_data?.xuData) {
return true;
}
const iterate_info = target_data.xuData.iterate_info;
func.runtime.render.sync_iterate_info_to_dataset(options._ds, iterate_info);
func.runtime.render.queue_hover_updates({
SESSION_ID: options.SESSION_ID,
paramsP: options.paramsP,
$div: $target,
_ds: options._ds,
});
return true;
};
func.runtime.render.handle_hover_out = function (options) {
if (options.is_skeleton) {
return false;
}
CLIENT_ACTIVITY_TS = Date.now();
func.runtime.render.set_hover_item(options.$container, null);
if (options._ds?.data_system) {
func.runtime.render.clear_hovered_attributes(options.SESSION_ID);
return true;
}
return false;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only widget rendering lives here so plugin runtime concerns stay separate from special-node routing.
func.runtime.widgets.render_node = async function (options) {
const exist_elm_obj = func.runtime.render.find_existing_element({
$container: options.$container,
nodeP: options.nodeP,
keyP: options.keyP,
render_context: options.render_context,
});
let $div = exist_elm_obj.div;
if (!$div) {
$div = await func.runtime.ui.create_container({
SESSION_ID: options.SESSION_ID,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
treeP: options.treeP,
parent_nodeP: options.parent_nodeP,
prop: options.prop,
classP: 'widget_wrapper',
});
if (func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID]) && func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
func.runtime.ui.empty($div);
}
const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, options.prop);
const { plugin_name, method, propsP, plugin: _plugin } = widget_context;
const report_error = function (descP, warn) {
return func.runtime.widgets.report_error(widget_context, descP, warn);
};
const definition = await func.runtime.widgets.get_definition(widget_context);
if (!func.runtime.widgets.supports_current_environment(definition)) {
return report_error(`plugin ${plugin_name} is not available in the current environment`, true);
}
const methods = definition?.methods || {};
if (methods && !methods[method]) {
return report_error('method not found');
}
const fields_ret = await func.runtime.widgets.get_fields_data(widget_context, methods[method].fields, propsP);
if (fields_ret.code < 0) {
return report_error(fields_ret.data);
}
const fields = fields_ret.data;
let exclude_attributes = [];
const prop_keys = Object.keys(propsP || {});
for (let prop_index = 0; prop_index < prop_keys.length; prop_index++) {
const key = prop_keys[prop_index];
if (typeof fields[key] !== 'undefined' || typeof fields[`xu-exp:${key}`] !== 'undefined') {
exclude_attributes.push(key);
}
}
await func.runtime.render.set_attributes_new({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
$elm: $div,
is_init: true,
execute_attributes: exclude_attributes,
});
func.runtime.ui.add_class($div, 'widget_wrapper');
if (!_plugin) {
return report_error(`plugin ${plugin_name} not found`);
}
if (_plugin.manifest['style.css'].exist) {
func.runtime.widgets.load_css_style(widget_context);
}
const plugin_setup_ret = await func.utils.get_plugin_setup(options.SESSION_ID, plugin_name);
if (plugin_setup_ret.code < 0) {
return report_error(plugin_setup_ret);
}
const api_utils = await func.common.get_module(options.SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID: options.SESSION_ID,
APP_OBJ,
dsSession: options.paramsP.dsSessionP,
job_id: options.jobNoP,
});
const params = func.runtime.widgets.build_params(
widget_context,
func.runtime.ui.get_first_node($div),
func.runtime.ui.get_data($div),
plugin_setup_ret.data,
api_utils,
);
const fx = await func.runtime.widgets.get_resource(widget_context, 'runtime.mjs');
await func.runtime.widgets.load_runtime_css(widget_context);
if (!fx[method]) {
throw `Method: ${method} does not exist`;
}
try {
await fx[method](fields, params);
} catch (err) {
func.utils.debug_report(options.SESSION_ID, `${plugin_name} widget`, err.message, 'E');
}
}
return $div;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only special node renderers live here so the core render tree can stay focused.
const normalize_runtime_tag_name = function (tag_name) {
return `${tag_name || ''}`.trim().toLowerCase();
};
const get_runtime_node_attributes = function (nodeP) {
if (!nodeP?.attributes || typeof nodeP.attributes !== 'object') {
return {};
}
return nodeP.attributes;
};
const get_runtime_node_content = function (nodeP) {
if (typeof nodeP?.content === 'string') {
return nodeP.content;
}
if (typeof nodeP?.text === 'string') {
return nodeP.text;
}
if (!Array.isArray(nodeP?.children)) {
return '';
}
return nodeP.children
.map(function (child) {
if (typeof child === 'string') {
return child;
}
if (typeof child?.content === 'string') {
return child.content;
}
if (typeof child?.text === 'string') {
return child.text;
}
return '';
})
.join('');
};
const get_runtime_asset_key = function (options, tag_name) {
const source_node = options.treeP || options.nodeP || {};
const parts = [
'xuda-html-asset',
options.paramsP?.prog_id || '',
tag_name,
source_node.id || source_node.id_org || '',
typeof options.keyP === 'undefined' || options.keyP === null ? '' : `${options.keyP}`,
].filter(function (part) {
return `${part || ''}`.trim() !== '';
});
return parts.join(':');
};
const get_runtime_asset_signature = function (attributes, content) {
const normalized_attributes = {};
const attr_keys = Object.keys(attributes || {}).sort();
for (let index = 0; index < attr_keys.length; index++) {
const key = attr_keys[index];
normalized_attributes[key] = attributes[key];
}
return JSON.stringify({
attributes: normalized_attributes,
content: `${content || ''}`,
});
};
const escape_runtime_asset_selector_value = function (value) {
const win = func.runtime.platform.get_window?.();
if (win?.CSS?.escape) {
return win.CSS.escape(value);
}
return `${value || ''}`.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
};
const find_runtime_head_asset = function (head, tag_name, asset_key) {
if (!head?.querySelector || !asset_key) {
return null;
}
return head.querySelector(`${tag_name}[data-xuda-asset-key="${escape_runtime_asset_selector_value(asset_key)}"]`);
};
const find_runtime_head_asset_by_attr = function (head, tag_name, attr_name, attr_value) {
if (!head?.querySelectorAll || !attr_name || !attr_value) {
return null;
}
const candidates = Array.from(head.querySelectorAll(tag_name));
return (
candidates.find(function (candidate) {
return candidate.getAttribute(attr_name) === attr_value || candidate[attr_name] === attr_value;
}) || null
);
};
const wait_for_runtime_asset_load = function (node) {
if (!node?.addEventListener) {
return Promise.resolve(node);
}
if (node.getAttribute?.('data-xuda-loaded') === 'true') {
return Promise.resolve(node);
}
if (!node.getAttribute?.('data-xuda-asset-key')) {
return Promise.resolve(node);
}
const tag_name = normalize_runtime_tag_name(node.tagName);
if (tag_name !== 'script' && !(tag_name === 'link' && normalize_runtime_tag_name(node.getAttribute?.('rel')) === 'stylesheet')) {
return Promise.resolve(node);
}
return new Promise(function (resolve) {
const done = function () {
node.setAttribute?.('data-xuda-loaded', 'true');
resolve(node);
};
node.addEventListener('load', done, { once: true });
node.addEventListener('error', done, { once: true });
});
};
const is_runtime_tailwind_script_src = function (src) {
const normalized_src = `${src || ''}`.trim().toLowerCase();
if (!normalized_src) {
return false;
}
return normalized_src.includes('cdn.tailwindcss.com') || /(?:^|\/)tailwind\.cdn\.js(?:[?#].*)?$/.test(normalized_src);
};
const is_runtime_tailwind_config_script = function (content) {
return /(?:^|[^\w.])tailwind\.config\s*=/.test(`${content || ''}`);
};
const has_runtime_tailwind = function () {
const win = func.runtime.platform.get_window?.();
return !!win?.tailwind;
};
const refresh_runtime_tailwind = async function () {
const win = func.runtime.platform.get_window?.();
const refresh = win?.tailwind?.refresh;
if (typeof refresh !== 'function') {
return false;
}
try {
await refresh();
return true;
} catch (error) {
console.error(error);
return false;
}
};
const remove_runtime_head_asset = function (node) {
if (node?.parentNode?.removeChild) {
node.parentNode.removeChild(node);
}
};
const apply_runtime_asset_metadata = function (node, asset_key, signature) {
if (!node?.setAttribute) {
return node;
}
if (asset_key) {
node.setAttribute('data-xuda-asset-key', asset_key);
}
node.setAttribute('data-xuda-asset-signature', signature);
return node;
};
const create_runtime_head_element = function (doc, tag_name, attributes, asset_key, signature) {
const node = doc.createElement(tag_name);
apply_runtime_asset_metadata(node, asset_key, signature);
func.runtime.platform.apply_element_attributes(node, attributes);
return node;
};
const get_runtime_html_script_queue = function () {
return (
func.runtime.render._html_script_queue ||
(func.runtime.render._html_script_queue = {
items: [],
running: false,
})
);
};
const queue_runtime_html_script_task = function (task) {
const queue = get_runtime_html_script_queue();
queue.items.push(task);
};
func.runtime.render.flush_html_script_queue = async function () {
const queue = get_runtime_html_script_queue();
if (queue.running || !queue.items.length) {
return;
}
queue.running = true;
try {
while (queue.items.length) {
const next_task = queue.items.shift();
await next_task();
}
} catch (error) {
console.error(error);
} finally {
queue.running = false;
}
};
const upsert_runtime_head_element = async function (options) {
const doc = func.runtime.platform.get_document?.();
const head = doc?.head;
const tag_name = normalize_runtime_tag_name(options.tag_name);
if (!doc?.createElement || !head?.appendChild || !tag_name) {
return null;
}
const asset_key = options.asset_key || '';
const signature = options.signature || '';
const attributes = options.attributes || {};
const content = typeof options.content === 'string' ? options.content : '';
const existing_by_key = find_runtime_head_asset(head, tag_name, asset_key);
if (existing_by_key && existing_by_key.getAttribute('data-xuda-asset-signature') === signature) {
return options.await_load ? await wait_for_runtime_asset_load(existing_by_key) : existing_by_key;
}
if (existing_by_key) {
remove_runtime_head_asset(existing_by_key);
}
if (options.find_existing_attr?.name && options.find_existing_attr?.value) {
const existing_by_attr = find_runtime_head_asset_by_attr(head, tag_name, options.find_existing_attr.name, options.find_existing_attr.value);
if (existing_by_attr) {
apply_runtime_asset_metadata(existing_by_attr, asset_key, signature);
existing_by_attr.setAttribute?.('data-xuda-loaded', 'true');
return existing_by_attr;
}
}
const node = create_runtime_head_element(doc, tag_name, attributes, asset_key, signature);
if (tag_name === 'script' && attributes.src && !Object.prototype.hasOwnProperty.call(attributes, 'async')) {
const script_type = normalize_runtime_tag_name(attributes.type);
if (script_type !== 'module') {
node.async = false;
}
}
if (tag_name === 'style' || (tag_name === 'script' && !attributes.src)) {
node.textContent = content;
}
head.appendChild(node);
if (tag_name !== 'script' && tag_name !== 'link') {
node.setAttribute('data-xuda-loaded', 'true');
}
return options.await_load ? await wait_for_runtime_asset_load(node) : node;
};
const render_runtime_html_asset = async function (options) {
if (options.is_skeleton) {
return options.$container;
}
const nodeP = options.treeP || options.nodeP || {};
const tag_name = normalize_runtime_tag_name(nodeP.tagName);
const attributes = { ...get_runtime_node_attributes(nodeP) };
const content = get_runtime_node_content(nodeP);
const asset_key = get_runtime_asset_key(options, tag_name);
const signature = get_runtime_asset_signature(attributes, content);
switch (tag_name) {
case 'title':
func.runtime.platform.set_title(content);
return options.$container;
case 'style':
await upsert_runtime_head_element({
tag_name,
attributes,
content,
asset_key,
signature,
});
return options.$container;
case 'meta':
if (!Object.keys(attributes).length) {
return options.$container;
}
await upsert_runtime_head_element({
tag_name,
attributes,
asset_key,
signature,
});
return options.$container;
case 'link': {
const href = `${attributes.href || ''}`.trim();
if (!href) {
return options.$container;
}
await upsert_runtime_head_element({
tag_name,
attributes,
asset_key,
signature,
find_existing_attr: {
name: 'href',
value: href,
},
await_load: normalize_runtime_tag_name(attributes.rel) === 'stylesheet',
});
return options.$container;
}
case 'script': {
const src = `${attributes.src || ''}`.trim();
const is_tailwind_script = is_runtime_tailwind_script_src(src);
const is_tailwind_config_script = is_runtime_tailwind_config_script(content);
if (!src && !content.trim()) {
return options.$container;
}
queue_runtime_html_script_task(async function () {
if (src && is_tailwind_script && has_runtime_tailwind()) {
return;
}
await upsert_runtime_head_element({
tag_name,
attributes,
content,
asset_key,
signature,
find_existing_attr: src
? {
name: 'src',
value: src,
}
: null,
await_load: !!src,
});
if (is_tailwind_config_script) {
await refresh_runtime_tailwind();
}
});
return options.$container;
}
default:
return null;
}
};
func.runtime.render.render_special_node = async function (options) {
const treeP = options.treeP || null;
const nodeP = options.nodeP || func.runtime.render.get_tree_source_node(treeP);
const render_tag_name = treeP?.tagName || nodeP?.tagName;
const normalized_render_tag_name = normalize_runtime_tag_name(render_tag_name);
const is_native_html_asset = ['title', 'style', 'meta', 'link', 'script'].includes(normalized_render_tag_name);
if (!is_native_html_asset && treeP?.content && nodeP?.attributes) {
nodeP.attributes['xu-content'] = treeP.content;
} else if (!is_native_html_asset && nodeP?.content && nodeP.attributes) {
nodeP.attributes['xu-content'] = nodeP.content;
}
const renderers = {
'xu-widget': async function () {
if (options.is_skeleton) return;
return await func.runtime.widgets.render_node({
SESSION_ID: options.SESSION_ID,
$container: options.$container,
$root_container: options.$root_container,
treeP,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
is_skeleton: options.is_skeleton,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
prop: options.prop,
render_context: options.render_context,
});
},
'xu-single-view': async function () {
const render_type = typeof options.paramsP?.renderType === 'string' ? options.paramsP.renderType.trim().toLowerCase() : '';
const view_renderer = ['grid', 'list'].includes(render_type) ? func.runtime.ui.render_multi_view_node : func.runtime.ui.render_single_view_node;
return await view_renderer({
SESSION_ID: options.SESSION_ID,
$container: options.$container,
$root_container: options.$root_container,
treeP,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
is_skeleton: options.is_skeleton,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
prop: options.prop,
render_context: options.render_context,
hover_handlers: options.hover_handlers,
iterate_child: options.iterate_child,
close_modal: options.close_modal,
});
},
'xu-multi-view': async function () {
return await func.runtime.ui.render_multi_view_node({
SESSION_ID: options.SESSION_ID,
$container: options.$container,
$root_container: options.$root_container,
treeP,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
is_skeleton: options.is_skeleton,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
iterate_child: options.iterate_child,
close_modal: options.close_modal,
});
},
'xu-panel': async function () {
return await func.runtime.ui.render_panel_node({
SESSION_ID: options.SESSION_ID,
$container: options.$container,
$root_container: options.$root_container,
treeP,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
is_skeleton: options.is_skeleton,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
prop: options.prop,
refreshed_ds: options.refreshed_ds,
});
},
title: async function () {
return await render_runtime_html_asset(options);
},
style: async function () {
return await render_runtime_html_asset(options);
},
meta: async function () {
return await render_runtime_html_asset(options);
},
link: async function () {
return await render_runtime_html_asset(options);
},
script: async function () {
return await render_runtime_html_asset(options);
},
};
const renderer = renderers[normalized_render_tag_name];
if (!renderer) {
return { handled: false };
}
return {
handled: true,
result: await renderer(),
};
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only child-iteration helpers live here so tree entrypoints can stay focused.
func.runtime.render.get_xu_for_iterators = function ($elm) {
const elm_data = func.runtime.ui.get_data($elm);
const custom_iterator_key = elm_data.xuData.iterator_key;
const custom_iterator_val = elm_data.xuData.iterator_val;
return {
iterator_key: custom_iterator_key || '_FOR_KEY',
iterator_val: custom_iterator_val || '_FOR_VAL',
is_key_dynamic_field: true,
is_val_dynamic_field: true,
};
};
func.runtime.render.attach_iterate_info_to_children = function ($divP, iterate_info) {
const el = func.runtime.ui.get_first_node($divP);
const children = el?.children ? Array.from(el.children) : [];
for (let index = 0; index < children.length; index++) {
const child_data = func.runtime.ui.get_data(children[index]);
if (child_data?.xuData) {
child_data.xuData.iterate_info = iterate_info;
}
}
return $divP;
};
func.runtime.render.can_iterate_children = function (nodeP, is_mobile) {
if (!nodeP) {
return false;
}
if (!is_mobile && nodeP.busy) {
return false;
}
return true;
};
func.runtime.render.mark_node_busy = function (nodeP) {
if (!nodeP) {
return false;
}
nodeP.busy = true;
return true;
};
func.runtime.render.release_node_busy = function (nodeP, delay = 1000) {
if (!nodeP) {
return false;
}
setTimeout(function () {
nodeP.busy = false;
}, delay);
return true;
};
func.runtime.render.render_child_nodes = async function (options) {
if (!options.nodeP?.children) {
return options.$divP;
}
if (options.before_record_function) {
await options.before_record_function();
}
if (options.nodeP.children.length) {
const render_tasks = new Array(options.nodeP.children.length);
for (let index = 0; index < options.nodeP.children.length; index++) {
const child = options.nodeP.children[index];
render_tasks[index] = options.render_child(index, child);
}
await Promise.all(render_tasks);
}
return options.$divP;
};
func.runtime.render.iterate_children = async function (options) {
if (!func.runtime.render.can_iterate_children(options.nodeP, options.is_mobile)) return;
func.runtime.render.mark_node_busy(options.nodeP);
const done = async function ($divP) {
func.runtime.render.release_node_busy(options.nodeP);
return $divP;
};
if (!options.nodeP || !options.nodeP.children) {
return await done(options.$divP);
}
await func.runtime.render.render_child_nodes({
$divP: options.$divP,
nodeP: options.nodeP,
before_record_function: options.before_record_function,
render_child: options.render_child,
});
return await done(options.$divP);
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only render tree entrypoints live here so draw/cache helpers can stay focused.
func.runtime.render.create_tree_runtime = function (options) {
const render_node = options.nodeP || func.runtime.render.get_tree_source_node(options.treeP);
const render_context = func.runtime.render.get_screen_context(options.SESSION_ID, options.$container, options.paramsP, options.is_skeleton);
const prop = func.runtime.render.get_node_attributes(options.treeP || render_node);
const is_mobile = render_context.is_mobile ? true : false;
const hover_handlers = func.runtime.render.create_hover_handlers({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$container: options.$container,
_ds: render_context._ds,
paramsP: options.paramsP,
});
const include_hover_click = func.runtime.render.should_bind_hover_click(options.paramsP, options.parent_infoP);
const close_modal = async function (modal_id) {
return await func.runtime.ui.close_modal_session(options.SESSION_ID, modal_id);
};
const iterate_child = async function ($divP, nodeP, parent_infoP, $root_container, before_record_function) {
const child_tree = await func.runtime.render.ensure_tree_node({
SESSION_ID: options.SESSION_ID,
nodeP: nodeP || options.treeP || render_node,
parent_infoP,
paramsP: options.paramsP,
keyP: options.keyP,
parent_nodeP: render_node,
pathP: options.treeP?.meta?.path || [],
});
return await func.runtime.render.iterate_children({
$divP,
nodeP: child_tree,
is_mobile,
before_record_function,
render_child: async function (key, child) {
await options.render_child($divP, child, parent_infoP, key, render_node, $root_container);
},
});
};
return {
render_context,
prop,
is_mobile,
hover_handlers,
include_hover_click,
close_modal,
iterate_child,
};
};
func.runtime.render.draw_node = async function (options) {
const draw_ret = await func.runtime.render.run_draw_pipeline({
SESSION_ID: options.SESSION_ID,
$container: options.$container,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
is_skeleton: options.is_skeleton,
keyP: options.keyP,
refreshed_ds: options.refreshed_ds,
parent_nodeP: options.parent_nodeP,
check_existP: options.check_existP,
$root_container: options.$root_container,
prop: options.prop,
element: options.treeP?.tagName || options.nodeP.tagName,
hover_handlers: options.hover_handlers,
include_hover_click: options.include_hover_click,
iterate_child: options.iterate_child,
buffered: !!glb.new_xu_render,
});
if (!glb.new_xu_render) {
return draw_ret.$div;
}
if (!draw_ret.$div) {
return draw_ret.$div;
}
return await func.runtime.render.finalize_buffered_draw({
SESSION_ID: options.SESSION_ID,
$div: draw_ret.$div,
temp_$div: draw_ret.temp_$div,
ret: draw_ret.ret,
paramsP: options.paramsP,
nodeP: options.nodeP,
});
};
func.runtime.render.render_tree = async function (treeP, renderer_context) {
if (!treeP) return;
const nodeP = func.runtime.render.get_tree_source_node(treeP);
const perf_end = func.runtime?.perf?.start?.(renderer_context.SESSION_ID, 'render_ui_tree');
func.runtime?.perf?.increment_map?.(renderer_context.SESSION_ID, 'render_node_counts', treeP.id || nodeP?.id || nodeP?.id_org || treeP.tagName || 'unknown');
try {
const tree_runtime = func.runtime.render.create_tree_runtime({
SESSION_ID: renderer_context.SESSION_ID,
$container: renderer_context.$container,
treeP,
nodeP,
parent_infoP: renderer_context.parent_infoP,
paramsP: renderer_context.paramsP,
jobNoP: renderer_context.jobNoP,
is_skeleton: renderer_context.is_skeleton,
keyP: renderer_context.keyP,
render_child: async function ($divP, child, parent_infoP, key, parentNodeP, rootContainerP) {
await func.runtime.render.render_tree(child, {
...renderer_context,
$container: $divP,
parent_infoP,
keyP: key,
refreshed_ds: null,
parent_nodeP: parentNodeP,
check_existP: null,
$root_container: rootContainerP,
});
},
});
const render_context = tree_runtime.render_context;
const _ds = render_context._ds;
const prop = tree_runtime.prop;
const hover_handlers = tree_runtime.hover_handlers;
const include_hover_click = tree_runtime.include_hover_click;
const close_modal = tree_runtime.close_modal;
const iterate_child = tree_runtime.iterate_child;
func.runtime.render.log_tree_debug({
SESSION_ID: renderer_context.SESSION_ID,
paramsP: renderer_context.paramsP,
nodeP,
_ds,
});
const special_render = await func.runtime.render.render_special_node({
SESSION_ID: renderer_context.SESSION_ID,
$container: renderer_context.$container,
$root_container: renderer_context.$root_container,
treeP,
nodeP,
parent_infoP: renderer_context.parent_infoP,
paramsP: renderer_context.paramsP,
jobNoP: renderer_context.jobNoP,
is_skeleton: renderer_context.is_skeleton,
keyP: renderer_context.keyP,
refreshed_ds: renderer_context.refreshed_ds,
parent_nodeP: renderer_context.parent_nodeP,
prop,
render_context,
hover_handlers,
iterate_child,
close_modal,
});
if (special_render.handled) {
func.runtime?.perf?.increment?.(renderer_context.SESSION_ID, 'render_special_node_hits');
return special_render.result;
}
return await func.runtime.render.draw_node({
SESSION_ID: renderer_context.SESSION_ID,
$container: renderer_context.$container,
$root_container: renderer_context.$root_container,
treeP,
nodeP,
parent_infoP: renderer_context.parent_infoP,
paramsP: renderer_context.paramsP,
jobNoP: renderer_context.jobNoP,
is_skeleton: renderer_context.is_skeleton,
keyP: renderer_context.keyP,
refreshed_ds: renderer_context.refreshed_ds,
parent_nodeP: renderer_context.parent_nodeP,
check_existP: renderer_context.check_existP,
prop,
hover_handlers,
include_hover_click,
iterate_child,
});
} finally {
perf_end?.();
}
};
func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nodeP, parent_infoP, paramsP, jobNoP, is_skeleton, keyP, refreshed_ds, parent_nodeP, check_existP, $root_container) {
if (!nodeP) return;
const treeP = await func.runtime.render.ensure_tree_node({
SESSION_ID,
nodeP,
parent_infoP,
paramsP,
keyP,
parent_nodeP,
});
const render_depth = func.runtime.render._html_script_render_depth || (func.runtime.render._html_script_render_depth = {});
render_depth[SESSION_ID] = (render_depth[SESSION_ID] || 0) + 1;
try {
return await func.runtime.render.render_tree(treeP, {
SESSION_ID,
$container,
parent_infoP,
paramsP,
jobNoP,
is_skeleton,
keyP,
refreshed_ds,
parent_nodeP,
check_existP,
$root_container,
});
} finally {
render_depth[SESSION_ID] = Math.max((render_depth[SESSION_ID] || 1) - 1, 0);
if (render_depth[SESSION_ID] === 0) {
await func.runtime.render.flush_html_script_queue?.();
delete render_depth[SESSION_ID];
}
}
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only attribute policy helpers live here so the phase engine can stay focused on execution order.
func.runtime.render.is_attribute_element_node = function (nodeP) {
if (!nodeP || !nodeP.attributes) {
return false;
}
if (nodeP.type === 'element') {
return true;
}
if (func.runtime.render.is_tree_node?.(nodeP)) {
return nodeP.kind !== 'text' && nodeP.kind !== 'placeholder';
}
const tag_name = typeof nodeP.tagName === 'string' ? nodeP.tagName.trim().toLowerCase() : '';
return !!tag_name && tag_name !== '#text' && tag_name !== '!doctype';
};
func.runtime.render.normalize_node_attributes = function (nodeP) {
if (!func.runtime.render.is_attribute_element_node(nodeP)) {
return {};
}
const attributes = nodeP.attributes;
const attribute_keys = Object.keys(attributes);
for (let index = 0; index < attribute_keys.length; index++) {
const key = attribute_keys[index];
let val = attributes[key];
if (key.substring(0, 6) === 'xu-exp') {
if (xu_isEmpty(val)) {
delete attributes[key];
continue;
}
const clean_key = key.split(':')[1];
if (typeof attributes[clean_key] !== 'undefined') {
delete attributes[clean_key];
}
}
if (glb.attr_abbreviations_arr.includes(key)) {
attributes[`xu-on:${key.substring(3)}`] = [
{
handler: 'custom',
props: {},
workflow: [
{
id: Date.now(),
data: {
action: 'update',
name: { value: val },
enabled: true,
},
props: {},
},
],
},
];
delete attributes[key];
}
}
const normalized_keys = Object.keys(attributes);
for (let index = 0; index < normalized_keys.length; index++) {
const key = normalized_keys[index];
let val = attributes[key];
val = func.runtime.render.fix_val_defaults(key, val);
if (typeof val === 'undefined' || val === null) {
delete attributes[key];
continue;
}
if (glb.solid_attributes.includes(key) && !val) {
delete attributes[key];
continue;
}
attributes[key] = val;
}
return attributes;
};
func.runtime.render.get_attribute_base_key = function (key) {
return key.split(':')[0];
};
func.runtime.render.get_expression_attribute_target = function (key) {
return key.split('xu-exp:')[1] || '';
};
func.runtime.render.is_panel_parameter_expression = function (nodeP, attr) {
if (nodeP?.tagName !== 'xu-panel' || !attr) {
return false;
}
return !['program', 'xu-render', 'xu-ref', 'xu-show'].includes(attr);
};
func.runtime.render.is_native_attribute = function (nodeTag, key) {
const new_key = func.runtime.render.get_attribute_base_key(key);
if (nodeTag === 'xu-panel' || nodeTag === 'xu-teleport') {
return false;
}
return !(new_key.substr(0, 2) === 'xu' && new_key.substr(2, 1) === '-');
};
func.runtime.render.should_abort = function ($container, ret) {
return !!(ret?.abort || func.runtime.ui.get_data($container)?.xuData?.pending_to_delete);
};
func.runtime.render.should_skip_phase_attribute = function (attr, execute_attributes = []) {
return glb.html5_events_handler.includes(attr) || execute_attributes.includes(attr);
};
func.runtime.render.has_attribute_or_expression = function (nodeP, attr) {
return !!(nodeP?.attributes?.hasOwnProperty(attr) || nodeP?.attributes?.hasOwnProperty(`xu-exp:${attr}`));
};
func.runtime.render.should_run_before_attribute = function (nodeP, attr) {
if (!func.runtime.render.has_attribute_or_expression(nodeP, attr)) {
return false;
}
if (!nodeP.attributes[`xu-exp:${attr}`] && nodeP?.attributes?.hasOwnProperty(attr) && typeof func.runtime.render.fix_val_defaults(attr, nodeP.attributes[attr]) === 'undefined') {
return false;
}
return true;
};
func.runtime.render.should_defer_runtime_attribute = function (nodeP, new_key) {
return new_key === 'xu-exp' || nodeP.attributes['xu-exp:' + new_key] || glb.run_xu_before.includes(new_key) || glb.run_xu_after.includes(new_key);
};
func.runtime.render.should_run_expression_attribute = function (attr, execute_attributes = [], done_exp, key) {
if (!attr) {
return false;
}
if (func.runtime.render.should_skip_phase_attribute(attr, execute_attributes)) {
return false;
}
if (done_exp?.has && done_exp.has(key)) {
return false;
}
if (done_exp?.includes && done_exp.includes(key)) {
return false;
}
return true;
};
func.runtime.render.should_run_after_attribute = function (nodeP, attr, execute_attributes = []) {
if (func.runtime.render.should_skip_phase_attribute(attr, execute_attributes)) {
return false;
}
if (!nodeP.attributes) {
return false;
}
return !!(nodeP.attributes[attr] || nodeP.attributes[`xu-exp:${attr}`]);
};
func.runtime.render.is_xu_tag = function (tagName) {
return typeof tagName === 'string' && tagName.substr(0, 3) === 'xu-';
};
func.runtime.render.execute_attribute = async function (options) {
return await func.runtime.render.execute_xu_function({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
xu_func: options.xu_func,
$elm: options.$elm,
$live_elm: options.$live_elm,
val: options.value_obj,
is_init: options.is_init,
refreshed_ds: options.refreshed_ds,
handler_bundle: options.handler_bundle,
get_params_obj_new: func.runtime.program.get_params_obj,
});
};
func.runtime.render.set_native_dom_attribute = function ($elm, key, val) {
try {
func.runtime.ui.set_attr($elm, key, val);
return true;
} catch (err) {
console.error(err.message);
return false;
}
};
func.runtime.render.store_xu_attribute = function ($elm, key, val) {
try {
const elm_data = func.runtime.ui.get_data($elm);
if (elm_data?.xuAttributes) {
elm_data.xuAttributes[key] = val;
if (func.runtime.ui.update_refresh_dependency_entry) {
func.runtime.ui.update_refresh_dependency_entry($elm, key, val);
}
return true;
}
} catch (error) {
console.error('[xuda-runtime] caught xuda_runtime.browser.attributes.policy.js:176:', error);
console.error(error);
}
return false;
};
func.runtime.render.record_attribute_stat = function ($elm, key, value) {
const elm_data = func.runtime.ui.get_data($elm);
if (!elm_data?.xuData) {
return false;
}
if (!elm_data.xuData.debug_info) {
elm_data.xuData.debug_info = {};
}
if (!elm_data.xuData.debug_info.attribute_stat) {
elm_data.xuData.debug_info.attribute_stat = {};
}
elm_data.xuData.debug_info.attribute_stat[key] = value;
return true;
};
func.runtime.render.fix_val_defaults = function (key, val) {
let ret = val;
if (key === 'xu-render' && (typeof val === 'undefined' || val === null || val === '')) {
ret = true;
}
if (key === 'xu-show' && (typeof val === 'undefined' || val === null || val === '')) {
ret = true;
}
return ret;
};
func.runtime.render.get_attribute_phase_plan = function (nodeP) {
if (!func.runtime.render.is_attribute_element_node(nodeP)) {
return {
has_xu_attrs: false,
before_attrs: [],
attribute_entries: [],
expression_entries: [],
after_attrs: [],
};
}
if (nodeP._runtime_attribute_phase_plan && nodeP._runtime_attribute_phase_plan_source === nodeP.attributes) {
return nodeP._runtime_attribute_phase_plan;
}
const attributes = nodeP.attributes || {};
const attribute_entries = [];
const expression_entries = [];
const html_event_entries = [];
const attribute_keys = Object.keys(attributes);
for (let index = 0; index < attribute_keys.length; index++) {
const key = attribute_keys[index];
const val = attributes[key];
const new_key = func.runtime.render.get_attribute_base_key(key);
attribute_entries.push({
key,
val,
new_key,
is_native: func.runtime.render.is_native_attribute(nodeP.tagName, key),
should_defer: func.runtime.render.should_defer_runtime_attribute(nodeP, new_key),
xu_func: new_key === 'xu-on' ? 'xu-on' : new_key,
});
if (glb.html5_events_handler.includes(key)) {
html_event_entries.push({
key,
});
}
const exp_attr = func.runtime.render.get_expression_attribute_target(key);
if (exp_attr) {
if (func.runtime.render.is_panel_parameter_expression(nodeP, exp_attr)) {
continue;
}
expression_entries.push({
key,
attr: exp_attr,
val,
});
}
}
const plan = {
has_xu_attrs: !!(attributes['xu-attrs'] || attributes['xu-exp:xu-attrs']),
before_attrs: glb.run_xu_before.filter(function (attr) {
return func.runtime.render.should_run_before_attribute(nodeP, attr);
}),
attribute_entries,
html_event_entries,
expression_entries,
after_attrs: glb.run_xu_after.filter(function (attr) {
return func.runtime.render.has_attribute_or_expression(nodeP, attr);
}),
};
nodeP._runtime_attribute_phase_plan_source = nodeP.attributes;
nodeP._runtime_attribute_phase_plan = plan;
return plan;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only attribute reading helpers live here so phase execution can stay focused.
func.runtime.render.create_attribute_reader = function (options) {
const done_exp = new Set();
const attr_value_cache = {};
const xu_exp_cache = {};
const elm_data = func.runtime.ui.get_data(options.$elm);
const xuData = elm_data?.xuData;
const get_xuExp = async function (attrib) {
if (Object.prototype.hasOwnProperty.call(xu_exp_cache, attrib)) {
return xu_exp_cache[attrib];
}
if (options.is_skeleton) return;
if (glb.new_xu_render) {
if (xuData && !xuData.attr_exp_info) {
xuData.attr_exp_info = {};
}
}
const attr = `xu-exp:${attrib}`;
if (!options.nodeP?.attributes?.hasOwnProperty(attr)) return;
const exp = options.nodeP.attributes[attr];
const res = await func.expression.get(options.SESSION_ID, exp, options.paramsP.dsSessionP, 'UI Attr EXP', options._ds.currentRecordId);
if (glb.new_xu_render && xuData) {
xuData.attr_exp_info[attrib] = res;
}
done_exp.add(attr);
xu_exp_cache[attrib] = res.result;
return xu_exp_cache[attrib];
};
const get_attr_value = async function (key) {
if (Object.prototype.hasOwnProperty.call(attr_value_cache, key)) {
return attr_value_cache[key];
}
let ret = func.runtime.render.fix_val_defaults(key, options.nodeP.attributes[key]);
if (options.nodeP?.attributes?.hasOwnProperty(`xu-exp:${key}`)) {
ret = await get_xuExp(key);
}
attr_value_cache[key] = ret;
return attr_value_cache[key];
};
return {
done_exp,
get_attr_value,
};
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only attribute phase execution lives here so policy and entrypoint concerns can stay focused.
func.runtime.render.apply_attribute_phases = async function (options) {
let _ret = {};
if (!func.runtime.render.is_attribute_element_node(options.nodeP)) return _ret;
func.runtime.render.normalize_node_attributes(options.nodeP);
const phase_plan = func.runtime.render.get_attribute_phase_plan(options.nodeP);
if (phase_plan.has_xu_attrs) {
const attr = 'xu-attrs';
await func.runtime.render.execute_attribute({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
xu_func: attr,
$elm: options.$elm,
$live_elm: options.$live_elm,
value_obj: {
key: attr,
value: await options.get_attr_value(attr),
},
is_init: options.is_init,
handler_bundle: options.handler_bundle,
});
}
if (!xu_isEmpty(options.nodeP.attributes)) {
for (let index = 0; index < phase_plan.before_attrs.length; index++) {
const attr = phase_plan.before_attrs[index];
if (func.runtime.render.should_abort(options.$container, _ret)) break;
if (func.runtime.render.should_skip_phase_attribute(attr, options.execute_attributes)) continue;
const ret = await func.runtime.render.execute_attribute({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
xu_func: attr,
$elm: options.$elm,
$live_elm: options.$live_elm,
value_obj: {
key: attr,
value: await options.get_attr_value(attr),
},
is_init: options.is_init,
handler_bundle: options.handler_bundle,
});
_ret = Object.assign(_ret, ret);
}
}
for (let index = 0; index < phase_plan.attribute_entries.length; index++) {
const entry = phase_plan.attribute_entries[index];
const key = entry.key;
const val = entry.val;
if (func.runtime.render.should_abort(options.$container, _ret)) break;
if (func.runtime.render.should_skip_phase_attribute(key, options.execute_attributes)) continue;
if (entry.is_native) {
func.runtime.render.set_native_dom_attribute(options.$elm, key, val);
continue;
}
func.runtime.render.store_xu_attribute(options.$elm, key, val);
if (entry.should_defer) {
continue;
}
if (entry.new_key === 'xu-on') {
const ret = await func.runtime.render.execute_attribute({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
xu_func: 'xu-on',
$elm: options.$elm,
$live_elm: options.$live_elm,
value_obj: {
key: key,
value: await options.get_attr_value(key),
},
is_init: options.is_init,
refreshed_ds: options.refreshed_ds,
handler_bundle: options.handler_bundle,
});
_ret = Object.assign(_ret, ret);
continue;
}
const ret = await func.runtime.render.execute_attribute({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
xu_func: entry.xu_func,
$elm: options.$elm,
$live_elm: options.$live_elm,
value_obj: {
key: key,
value: await options.get_attr_value(key),
},
is_init: options.is_init,
refreshed_ds: options.refreshed_ds,
handler_bundle: options.handler_bundle,
});
_ret = Object.assign(_ret, ret);
}
for (let index = 0; index < phase_plan.expression_entries.length; index++) {
const entry = phase_plan.expression_entries[index];
const key = entry.key;
const val = entry.val;
if (func.runtime.render.should_abort(options.$container, _ret)) break;
const attr = entry.attr;
if (!func.runtime.render.should_run_expression_attribute(attr, options.execute_attributes, options.done_exp, key)) {
continue;
}
const ret = await func.runtime.render.execute_attribute({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
xu_func: 'xu-exp',
$elm: options.$elm,
$live_elm: options.$live_elm,
value_obj: {
key: attr,
value: val,
},
is_init: true,
refreshed_ds: options.refreshed_ds,
handler_bundle: options.handler_bundle,
});
_ret = Object.assign(_ret, ret);
}
for (let index = 0; index < phase_plan.after_attrs.length; index++) {
const attr = phase_plan.after_attrs[index];
if (func.runtime.render.should_abort(options.$container, _ret)) break;
if (!func.runtime.render.should_run_after_attribute(options.nodeP, attr, options.execute_attributes)) continue;
const ret = await func.runtime.render.execute_attribute({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
xu_func: attr,
$elm: options.$elm,
$live_elm: options.$live_elm,
value_obj: {
key: attr,
value: await options.get_attr_value(attr),
},
is_init: options.is_init,
refreshed_ds: options.refreshed_ds,
handler_bundle: options.handler_bundle,
});
_ret = Object.assign(_ret, ret);
}
return _ret;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only attribute entrypoint lives here so reader and phase execution can evolve independently.
func.runtime.render.normalize_set_attributes_options = function (...args) {
if (typeof args[0] === 'object' && args[0] !== null && !Array.isArray(args[0])) {
return {
...args[0],
$live_elm: args[0].$live_elm || args[0].$elm,
};
}
const [SESSION_ID, is_skeleton, $root_container, nodeP, $container, paramsP, parent_infoP, jobNoP, keyP, parent_nodeP, $elm, is_init, execute_attributes = [], refreshed_ds] = args;
return {
SESSION_ID,
is_skeleton,
$root_container,
nodeP,
$container,
paramsP,
parent_infoP,
jobNoP,
keyP,
parent_nodeP,
$elm,
$live_elm: $elm,
is_init,
execute_attributes,
refreshed_ds,
};
};
func.runtime.render.set_attributes_new = async function (...args) {
const options = func.runtime.render.normalize_set_attributes_options(...args);
const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[options.paramsP.dsSessionP];
if (!_ds) return { abort: true };
if (func.runtime.ui.prune_stale_refresh_dependencies) {
func.runtime.ui.prune_stale_refresh_dependencies(options.$elm, options.nodeP?.attributes || {});
}
const attr_reader = func.runtime.render.create_attribute_reader({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$elm: options.$elm,
nodeP: options.nodeP,
paramsP: options.paramsP,
_ds,
});
const handler_bundle = func.runtime.render.build_xu_handlers({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
$elm: options.$elm,
$live_elm: options.$live_elm,
is_init: options.is_init,
refreshed_ds: options.refreshed_ds,
get_params_obj_new: func.runtime.program.get_params_obj,
}, _ds);
const _ret = await func.runtime.render.apply_attribute_phases({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
$elm: options.$elm,
$live_elm: options.$live_elm,
is_init: options.is_init,
execute_attributes: options.execute_attributes || [],
refreshed_ds: options.refreshed_ds,
done_exp: attr_reader.done_exp,
get_attr_value: attr_reader.get_attr_value,
handler_bundle,
});
const phase_plan = func.runtime.render.get_attribute_phase_plan(options.nodeP);
for (let index = 0; index < phase_plan.html_event_entries.length; index++) {
const key = phase_plan.html_event_entries[index].key;
if (func.runtime.render.should_abort(options.$container, _ret)) break;
func.runtime.ui.set_attr(options.$elm, key, await attr_reader.get_attr_value(key));
}
return _ret;
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only data-bound xu handlers live here so the main handler dispatcher can stay focused.
func.runtime.render.handle_xu_ref = async function (options) {
if (!options.val.value) {
return {};
}
func.UI.update_xu_ref(options.SESSION_ID, options.dsSession || options.paramsP.dsSessionP, options.val.value, options.$elm);
const targetNode = func.runtime.ui.get_first_node(options.$elm);
if (!targetNode) {
return {};
}
const target_data = func.runtime.ui.get_data(options.$elm);
if (target_data?.xuData?.xu_ref_observer) {
target_data.xuData.xu_ref_observer.disconnect();
}
// Build-400 parity: refresh on ANY mutation. The xu-ref refresh loop is prevented upstream in
// update_xu_ref (clean snapshot, empty-transient guard, $elm-gated change detection), so cosmetic
// DOM-only mutations resolve to "no change" and never cascade.
// Filtering the mutations here was a redundant second mechanism and risked suppressing a real
// refresh; build 400's observer is unfiltered. The disconnect above (absent in build 400) still
// prevents observers accumulating across re-renders.
const observer = new MutationObserver(function () {
// Ignore the DOM moves the preserve mechanism makes itself (detach/reattach of a [data-xu-preserve]
// node across a panel re-render). They are internal bookkeeping, not a real data/user change, and
// reacting to them would re-enter refresh_screen -> re-render -> reattach -> ... an infinite loop.
if (func.runtime.ui.__xu_preserve_suppress) return;
func.runtime.ui.refresh_xu_attributes({
SESSION_ID: options.SESSION_ID,
fields_arr: [options.val.value],
});
});
observer.observe(targetNode, { attributes: true, childList: true, subtree: true });
if (target_data?.xuData) {
target_data.xuData.xu_ref_observer = observer;
}
return {};
};
func.runtime.render.handle_xu_bind = async function (options) {
if (options.is_skeleton) return {};
const $elm = func.runtime?.ui?.get_preferred_live_element ? func.runtime.ui.get_preferred_live_element(options.$elm) : options.$elm;
const elm_data = func.runtime.ui.get_data($elm);
const xuData = elm_data?.xuData;
const bind_expression =
typeof options?.val?.value === 'string'
? options.val.value
: elm_data?.xuAttributes?.['xu-bind'] || func.runtime.ui.get_attr($elm, 'xu-bind');
if (!xuData?.paramsP) {
return {};
}
if (!bind_expression) {
return {};
}
let val_is_reference_field = false;
let _prog_id = xuData.paramsP.prog_id;
let _dsP = xuData.paramsP.dsSessionP;
let is_dynamic_field = false;
let field_prop;
let bind_field_id;
const _bind_elm_node = func.runtime.ui.get_first_node($elm);
const input_field_type = _bind_elm_node?.type || func.runtime.ui.get_attr($elm, 'type');
const bind_ui_id = xuData.ui_id || func.runtime.ui.get_attr($elm, 'xu-ui-id');
const get_bind_targets = function () {
const $root = func.runtime.ui.get_refresh_index_root?.(options.SESSION_ID) || func.runtime.ui.get_root_element?.(options.SESSION_ID) || func.runtime.ui._wrap_matches([document.body]);
const live_targets = [];
const seen_nodes = new Set();
const add_candidate = function (node) {
if (!node || seen_nodes.has(node)) {
return;
}
const target_data = func.runtime.ui.get_data(node);
if (!node.isConnected || target_data?.xuData?.pending_to_delete) {
return;
}
seen_nodes.add(node);
live_targets.push(node);
};
if (bind_ui_id && options.SESSION_ID && func.runtime?.ui?.find_refresh_elements_by_attr) {
const ui_id_targets = func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-ui-id', bind_ui_id).toArray();
for (let index = 0; index < ui_id_targets.length; index++) {
add_candidate(ui_id_targets[index]);
}
}
const runtime_nodes = func.runtime?.ui?.get_refresh_index_elements
? func.runtime.ui.get_refresh_index_elements(options.SESSION_ID, $root).toArray()
: func.runtime.ui.find_by_selector($root, '[xu-ui-id]').toArray();
const target_parent_ui_id = xuData.parent_element_ui_id;
const target_node_id = xuData.nodeid;
const target_recordid = xuData.recordid;
const target_prog_id = xuData.paramsP?.prog_id;
for (let index = 0; index < runtime_nodes.length; index++) {
const node = runtime_nodes[index];
const node_data = func.runtime.ui.get_data(node);
const node_xu_data = node_data?.xuData;
if (!node_xu_data) {
continue;
}
if (target_node_id && node_xu_data.nodeid !== target_node_id) {
continue;
}
if (target_parent_ui_id && node_xu_data.parent_element_ui_id !== target_parent_ui_id) {
continue;
}
if (target_prog_id && node_xu_data.paramsP?.prog_id !== target_prog_id) {
continue;
}
if (typeof target_recordid !== 'undefined' && target_recordid !== null && node_xu_data.recordid !== target_recordid) {
continue;
}
const node_bind_expression = node_data?.xuAttributes?.['xu-bind'] || func.runtime.ui.get_attr(node, 'xu-bind');
if (node_bind_expression !== bind_expression) {
continue;
}
add_candidate(node);
}
if (!live_targets.length) {
const _node = func.runtime.ui.get_first_node($elm);
return func.runtime.ui._wrap_matches(_node ? [_node] : []);
}
for (let index = 0; index < live_targets.length; index++) {
const node = live_targets[index];
const target_data = func.runtime.ui.get_data(node);
if (!node?.isConnected || target_data?.xuData?.pending_to_delete) {
continue;
}
add_candidate(node);
}
if (!live_targets.length) {
const _node = func.runtime.ui.get_first_node($elm);
return func.runtime.ui._wrap_matches(_node ? [_node] : []);
}
const target_nodes = live_targets;
const visible_targets = [];
for (let index = 0; index < target_nodes.length; index++) {
const node = target_nodes[index];
if (node.getClientRects?.().length && !node.hidden) {
visible_targets.push(node);
}
}
return func.runtime.ui._wrap_matches(visible_targets.length ? visible_targets : live_targets);
};
try {
const bind_field = await func.runtime.bind.resolve_field(options.SESSION_ID, _prog_id, _dsP, bind_expression.split('.')[0], xuData.iterate_info);
bind_field_id = bind_field.bind_field_id;
field_prop = bind_field.field_prop;
is_dynamic_field = bind_field.is_dynamic_field;
_dsP = bind_field.dsSessionP;
_prog_id = bind_field.prog_id;
val_is_reference_field = true;
} catch (err) {
console.error(err?.message || err);
return {};
}
const bind = func.runtime.bind.get_adapter(options.SESSION_ID);
const get_changed_node = function (event) {
const event_target = func.runtime.bind.get_bind_value_node(event?.target);
if (func.runtime.bind.is_value_node(event_target) && event_target.isConnected) {
return event_target;
}
const current_target = func.runtime.bind.get_bind_value_node(event?.currentTarget);
if (current_target?.nodeType && current_target.isConnected) {
return current_target;
}
return func.runtime.bind.get_bind_value_node(options.$elm);
};
const get_changed_input_type = function (node) {
return (node?.type || node?.getAttribute?.('type') || input_field_type || '').toLowerCase();
};
const field_changed = function (event) {
const pending_update = (async function () {
const changed_node = get_changed_node(event);
if (!changed_node) {
return;
}
if (changed_node.tagName?.toLowerCase?.() === 'select') {
changed_node.__xuda_select_last_user_change_ts = Date.now();
}
const changed_input_type = get_changed_input_type(changed_node);
const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[_dsP];
const field_type = func.runtime.bind.get_field_type(field_prop);
if (field_type === 'array' && changed_input_type === 'checkbox' && val_is_reference_field) {
const arr_value_before_cast = [...(await func.datasource.get_value(options.SESSION_ID, bind_field_id, _dsP, _ds.currentRecordId)).ret.value];
const value_from_getter = bind.getter(changed_node);
const value = func.runtime.bind.toggle_array_value(arr_value_before_cast, value_from_getter);
const datasource_changes = func.runtime.bind.build_datasource_changes(_dsP, _ds.currentRecordId, bind_field_id, value);
return await func.datasource.update(options.SESSION_ID, datasource_changes);
}
if (field_type === 'array' && changed_input_type === 'radio' && val_is_reference_field) {
const value_from_getter = bind.getter(changed_node);
const datasource_changes = func.runtime.bind.build_datasource_changes(_dsP, _ds.currentRecordId, bind_field_id, [value_from_getter]);
return await func.datasource.update(options.SESSION_ID, datasource_changes);
}
const raw_value = func.runtime.bind.normalize_raw_value(changed_node, field_prop, bind.getter(changed_node));
const value = await func.runtime.bind.get_cast_value(options.SESSION_ID, field_prop, changed_input_type, raw_value);
func.runtime.bind.remember_select_numeric_context(changed_node, field_type, value);
if (!_ds.currentRecordId) return;
const iterate_info = xuData.iterate_info;
const is_iterate_field = is_dynamic_field && iterate_info && (iterate_info.iterator_val === bind_field_id || iterate_info.iterator_key === bind_field_id);
if (!is_iterate_field) {
const datasource_changes = func.runtime.bind.build_datasource_changes(_dsP, _ds.currentRecordId, bind_field_id, value);
await func.datasource.update(options.SESSION_ID, datasource_changes);
}
await func.runtime.bind.update_reference_source_array({
SESSION_ID: options.SESSION_ID,
dsSessionP: _dsP,
currentRecordId: _ds.currentRecordId,
iterate_info,
bind_field_id,
field_prop,
val_is_reference_field,
input_field_type: changed_input_type,
expression_value: bind_expression,
value,
});
await func.datasource.update_changes_for_out_parameter(options.SESSION_ID, _dsP, _ds.parentDataSourceNo);
})().catch(function (error) {
console.error(error);
});
return func.runtime.bind.track_pending_update(options.SESSION_ID, pending_update);
};
const bind_targets = get_bind_targets();
const target_nodes = bind_targets.toArray();
for (let index = 0; index < target_nodes.length; index++) {
const target = target_nodes[index];
const target_data = func.runtime.ui.get_data(target);
const listener_node = func.runtime.bind.get_bind_value_node(target) || target;
const bind_listener_mode = func.runtime.bind.should_use_live_text_listener(listener_node) ? 'live_text' : 'default';
if (
!target_data?.xuData ||
(
target_data.xuData.bind_listener_attached &&
target_data.xuData.bind_listener_mode === bind_listener_mode &&
target_data.xuData.bind_listener_node === listener_node
)
) {
continue;
}
bind.listener(listener_node, field_changed);
target_data.xuData.bind_listener_attached = true;
target_data.xuData.bind_listener_mode = bind_listener_mode;
target_data.xuData.bind_listener_node = listener_node;
}
const set_value = async function () {
const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[_dsP];
if (!_ds) return;
const target_record_id = xuData.recordid || _ds.currentRecordId;
if (!target_record_id) return;
let value;
try {
if (val_is_reference_field) {
const iter = xuData.iterate_info;
if (iter && is_dynamic_field && (iter.iterator_val === bind_field_id || iter.iterator_key === bind_field_id)) {
value = iter.iterator_val === bind_field_id ? iter._val : iter._key;
} else {
const resolved_value = await func.datasource.get_value(options.SESSION_ID, bind_field_id, _dsP, target_record_id);
if (resolved_value?.found) {
value = resolved_value.ret.value;
} else {
value = func.runtime.bind.get_source_value(_ds, bind_field_id, is_dynamic_field);
}
}
value = func.runtime.bind.format_display_value($elm, field_prop, bind_field_id, bind_expression, value, input_field_type);
} else {
value = bind_expression;
}
if (typeof value === 'undefined') return;
const live_bind_targets = get_bind_targets().toArray();
for (let index = 0; index < live_bind_targets.length; index++) {
const elm = func.runtime.bind.get_bind_value_node(live_bind_targets[index]) || live_bind_targets[index];
if (!elm) {
continue;
}
const target_input_type = elm.type || input_field_type;
const is_focused_live_text_input = func.runtime.bind.should_use_live_text_listener(elm) && document.activeElement === elm;
if (is_focused_live_text_input && typeof elm.value !== 'undefined' && !xu_isEqual(elm.value, value === null ? '' : String(value))) {
continue;
}
switch (target_input_type) {
case 'radio':
bind.setter(elm, value);
elm.checked = elm.value === String(value);
break;
case 'checkbox':
bind.setter(elm, value);
elm.checked = !!value;
break;
default:
if (elm.tagName?.toLowerCase?.() === 'select' && func.runtime.bind.set_select_value(elm, value, func.runtime.bind.get_field_type(field_prop))) {
break;
}
bind.setter(elm, value);
if (typeof elm.value !== 'undefined') {
elm.value = value === null ? '' : String(value);
}
break;
}
}
} catch (err) {
console.error(err);
}
};
const bind_refresh_event = 'xu-bind-refresh.' + _dsP.toString();
const bind_handler_key = '_xuda_bind_' + (bind_ui_id || bind_field_id || 'bind');
if (document.body[bind_handler_key]) {
document.body.removeEventListener(bind_refresh_event, document.body[bind_handler_key]);
}
document.body[bind_handler_key] = async () => {
await set_value();
};
document.body.addEventListener(bind_refresh_event, document.body[bind_handler_key]);
await set_value();
return {};
};
func.runtime.render.apply_xu_class = async function (options) {
try {
const raw_value = options?.val?.value;
const xuData = func.runtime.ui.get_data(options.$elm)?.xuData;
if (typeof raw_value === 'string') {
const trimmed = raw_value.trim();
const looks_like_object = trimmed.startsWith('{') && trimmed.endsWith('}');
if (!looks_like_object) {
func.runtime.render.apply_expression_class(options.$elm, raw_value);
xuData.debug_info.attribute_stat['xu-class'] = func.runtime.ui.get_first_node(options.$elm)?.className || func.runtime.ui.get_attr(options.$elm, 'class');
return {};
}
}
const classes_obj = typeof raw_value === 'string' ? JSON5.parse(raw_value) : Object.assign({}, {}, raw_value);
if (typeof classes_obj !== 'object' || classes_obj === null || Array.isArray(classes_obj)) {
throw new Error('xu-class expects an object map or a class string');
}
const class_names = Object.keys(classes_obj);
for (let index = 0; index < class_names.length; index++) {
const cla = class_names[index];
const cond = classes_obj[cla];
const res = await func.expression.get(
options.SESSION_ID,
cond,
options.paramsP.dsSessionP,
'UI Attr EXP',
xuData.currentRecordId,
null,
null,
null,
null,
null,
xuData.iterate_info,
);
if (res.result) {
func.runtime.ui.add_class(options.$elm, cla);
} else {
func.runtime.ui.remove_class(options.$elm, cla);
}
}
xuData.debug_info.attribute_stat['xu-class'] = func.runtime.ui.get_first_node(options.$elm)?.className || func.runtime.ui.get_attr(options.$elm, 'class');
return {};
} catch (e) {
await func.runtime.render.report_xu_runtime_error(
{
...options,
xu_func: 'xu-class',
val: {
key: 'xu-class',
value: options?.val?.value,
},
},
e,
'xu-class has invalid syntax',
);
return { abort: true };
}
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only iteration handlers live here so xu-for flow can evolve separately from data binding.
// XU_PERF keyed reuse -------------------------------------------------------
// execute_xu_for stashes the existing row elements here (instead of tearing
// them down) so handle_xu_for can reuse unchanged rows. Any stash entry that
// is not consumed gets removed by execute_xu_for right after the re-render,
// so a mismatch degrades to the legacy full rebuild, never to duplicate or
// stale rows.
func.runtime.render.xu_for_reuse_stash = func.runtime.render.xu_for_reuse_stash || new Map();
// Stable identity for a row across renders: prefer the item's own id, fall
// back to the position for id-less objects, or the value itself for scalars.
func.runtime.render.get_xu_row_key = function (_val, idx) {
if (_val && typeof _val === 'object') {
const id = typeof _val.id !== 'undefined' ? _val.id : (typeof _val._id !== 'undefined' ? _val._id : _val.ROWID);
return typeof id === 'undefined' ? 'xidx:' + idx : 'xid:' + String(id);
}
return 'xval:' + String(_val);
};
// Value snapshot taken at render time; a row is reused only when its current
// item deep-equals the snapshot. Unsnapshottable values never compare equal,
// which forces a safe re-render.
func.runtime.render.snap_xu_row = function (_val) {
if (_val === null || typeof _val !== 'object') return _val;
try {
return structuredClone(_val);
} catch (e) {
return undefined;
}
};
// Resolve the LIVE container for an xu-for whose captured reference went stale.
// The row handler is asynchronous, so a concurrent panel refresh can replace its
// subtree while rows are rendering. The stable program node id is the only
// reliable link between the detached container and the visible replacement.
func.runtime.render.resolve_live_xu_for_container = function (SESSION_ID, $container, row_node_id) {
const container_data = func.runtime.ui.get_data($container)?.xuData || {};
const container_ui_id = func.runtime.ui.get_attr($container, 'xu-ui-id');
const root_node = document.body;
if (container_ui_id && root_node?.querySelector) {
const escaped_ui_id = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(container_ui_id) : container_ui_id;
const by_id = root_node.querySelector(`[xu-ui-id="${escaped_ui_id}"]`);
if (by_id && by_id.isConnected) return { node: by_id, via: 'xu-ui-id' };
}
if (container_data.nodeid && func.runtime.ui.get_refresh_indexed_elements_by_node_id) {
const indexed = func.runtime.ui.get_refresh_indexed_elements_by_node_id(SESSION_ID, container_data.nodeid).toArray();
for (let indexed_i = 0; indexed_i < indexed.length; indexed_i++) {
const candidate = indexed[indexed_i];
if (!candidate?.isConnected) continue;
const candidate_data = func.runtime.ui.get_data(func.runtime.ui._wrap_matches([candidate]))?.xuData || {};
if (typeof container_data.recordid === 'undefined' || candidate_data.recordid === container_data.recordid) {
return { node: candidate, via: 'refresh-index' };
}
}
}
if (root_node?.querySelectorAll) {
const all_ui_elements = root_node.querySelectorAll('[xu-ui-id]');
let any_record_fallback = null;
let row_parent_fallback = null;
for (let scan_i = 0; scan_i < all_ui_elements.length; scan_i++) {
const el = all_ui_elements[scan_i];
if (!el.isConnected) continue;
const el_data = func.runtime.ui.get_data(func.runtime.ui._wrap_matches([el]))?.xuData;
if (!el_data) continue;
if (container_data.nodeid && el_data.nodeid === container_data.nodeid) {
if (typeof container_data.recordid === 'undefined' || el_data.recordid === container_data.recordid) {
return { node: el, via: 'dom-scan' };
}
if (!any_record_fallback) any_record_fallback = el;
}
// A visible row rendered from this template identifies its live parent even
// when the container metadata disappeared with the stale subtree.
if (!row_parent_fallback && row_node_id && el_data.nodeid === row_node_id && el.parentElement) {
row_parent_fallback = el.parentElement;
}
}
if (any_record_fallback) return { node: any_record_fallback, via: 'dom-scan-anyrecord' };
if (row_parent_fallback) return { node: row_parent_fallback, via: 'row-parent' };
}
return { node: null, via: null };
};
// A later invocation for the same row template makes earlier deferred work stale.
func.runtime.render._xu_for_latest_run = func.runtime.render._xu_for_latest_run || new Map();
func.runtime.render.handle_xu_for = async function (options) {
if (options.parent_infoP?.iterate_info) return {};
if (!options.data.value) return {};
try {
const $live_elm = options.$live_elm || options.$elm;
const _run_token = (func.runtime.render._xu_for_latest_run.get(options.nodeP?.id) || 0) + 1;
func.runtime.render._xu_for_latest_run.set(options.nodeP?.id, _run_token);
const _is_latest_run = function () {
return func.runtime.render._xu_for_latest_run.get(options.nodeP?.id) === _run_token;
};
// Adopt a visible replacement before rendering. Without this, all rows land
// in a detached copy and the visible list remains frozen at its first row.
try {
const _container_node = func.runtime.ui.get_first_node(options.$container);
let _pending_attach_buffer = false;
if (_container_node && !_container_node.isConnected) {
let _detached_root = _container_node;
while (_detached_root.parentNode && _detached_root.parentNode.nodeType === 1) _detached_root = _detached_root.parentNode;
_pending_attach_buffer = _detached_root.tagName === 'TEMPLATE' || _detached_root.tagName === 'TMP';
}
if ((!_container_node || !_container_node.isConnected) && !_pending_attach_buffer) {
const _resolved = func.runtime.render.resolve_live_xu_for_container(options.SESSION_ID, options.$container, options.nodeP?.id);
if (_resolved.node && func.runtime.ui._wrap_matches) {
options.$container = func.runtime.ui._wrap_matches([_resolved.node]);
const _adopted_children = func.runtime.ui.get_children(options.$container);
let _cleared = 0;
for (let stale_index = 0; stale_index < _adopted_children.length; stale_index++) {
const _stale_child = _adopted_children[stale_index];
if (func.runtime.ui.get_data(_stale_child)?.xuData?.nodeid === options.nodeP?.id) {
func.runtime.ui.remove(_stale_child);
_cleared++;
}
}
console.log('[xufor-fix] adopted live container via ' + _resolved.via + ', cleared ' + _cleared + ' stale rows');
}
}
} catch (stale_resolution_error) {
console.log('[xufor-fix] resolution error', stale_resolution_error);
}
const { arr, reference_source_obj } = await func.runtime.render.resolve_xu_for_source(options.SESSION_ID, options.paramsP.dsSessionP, options.data.value);
const { iterator_key, iterator_val, is_key_dynamic_field, is_val_dynamic_field } = func.runtime.render.get_xu_for_iterators(options.$elm);
const _progFields = await func.datasource.get_progFields(options.SESSION_ID, options.paramsP.dsSessionP);
let i = 0;
const render_row = async function (_key, _val) {
const currentRecordId = SESSION_OBJ[options.SESSION_ID].DS_GLB[options.paramsP.dsSessionP].currentRecordId.toString();
const iterate_info = func.runtime.render.build_iterate_info({
_val,
_key,
iterator_key,
iterator_val,
is_key_dynamic_field,
is_val_dynamic_field,
reference_source_obj,
});
func.runtime.render.apply_iterate_info_to_current_record(options.SESSION_ID, options.paramsP.dsSessionP, currentRecordId, _progFields, iterate_info);
// XU_PERF: parent_infoP.iterate_info carries reference_source_obj (the
// whole source array); structuredClone per row is quadratic. iterate_info
// is replaced right below, so a shallow copy is sufficient.
const _parent_info = glb.XU_PERF ? Object.assign({}, options.parent_infoP) : (structuredClone(options.parent_infoP) || {});
_parent_info.iterate_info = iterate_info;
const $divP = await func.runtime.render.render_ui_tree(options.SESSION_ID, options.$container, options.nodeP, _parent_info, options.paramsP, options.jobNoP, null, i, null, options.nodeP, null, options.$root_container);
func.runtime.render.attach_iterate_info_to_children($divP, iterate_info);
i++;
return $divP;
};
if (Array.isArray(arr)) {
let reuse_pool = null;
let ordered = null;
if (glb.XU_PERF) {
ordered = [];
const parent_node = func.runtime.ui.get_first_node(options.$container);
const parent_ui_id = parent_node?.getAttribute?.('xu-ui-id');
const stash_key = parent_ui_id + '::' + options.nodeP.id;
const stashed = func.runtime.render.xu_for_reuse_stash.get(stash_key);
if (stashed) {
func.runtime.render.xu_for_reuse_stash.delete(stash_key);
// Rows reused as-is keep their rendered index bindings; when the
// template reads the key iterator, indexes shift on reorder, so
// fall back to the legacy rebuild for correctness.
const template_reads_index = func.runtime.render.xu_for_template_reads_key(options.nodeP, iterator_key);
if (template_reads_index) {
const _stale = stashed.filter(function (el) { return el && el.isConnected; });
if (_stale.length) func.runtime.ui.remove(func.runtime.ui._wrap_matches(_stale));
} else {
reuse_pool = new Map();
for (let s = 0; s < stashed.length; s++) {
const el = stashed[s];
if (el && el.isConnected && typeof el.__xu_row_key !== 'undefined' && !reuse_pool.has(el.__xu_row_key)) {
reuse_pool.set(el.__xu_row_key, el);
} else if (el && el.isConnected) {
func.runtime.ui.remove(func.runtime.ui._wrap_matches([el]));
}
}
}
}
}
for (let idx = 0; idx < arr.length; idx++) {
const _val = arr[idx];
if (glb.XU_PERF) {
const row_key = func.runtime.render.get_xu_row_key(_val, idx);
if (reuse_pool) {
const el = reuse_pool.get(row_key);
if (el) {
reuse_pool.delete(row_key);
if (xu_isEqual(el.__xu_row_snap, _val)) {
ordered.push(el);
i++;
continue; // unchanged row: keep its DOM untouched
}
// same key, changed value: rebuild the row, drop the old element
func.runtime.ui.remove(func.runtime.ui._wrap_matches([el]));
}
}
const $divP = await render_row(idx, _val);
const _row_node = func.runtime.ui.get_first_node($divP);
if (_row_node) {
_row_node.__xu_row_key = row_key;
_row_node.__xu_row_snap = func.runtime.render.snap_xu_row(_val);
ordered.push(_row_node);
}
} else {
await render_row(idx, _val);
}
}
if (reuse_pool) {
// rows whose keys vanished from the source
reuse_pool.forEach(function (el) {
if (el && el.isConnected) func.runtime.ui.remove(func.runtime.ui._wrap_matches([el]));
});
}
if (ordered && ordered.length > 1) {
// single forward pass: pull each row behind its predecessor
let prev = null;
for (let o = 0; o < ordered.length; o++) {
const el = ordered[o];
if (!el || !el.isConnected) continue;
if (prev && prev.nextSibling !== el) {
prev.parentElement.insertBefore(el, prev.nextSibling);
}
prev = el;
}
}
} else {
const iterate_keys = Object.keys(arr || {});
for (let idx = 0; idx < iterate_keys.length; idx++) {
const _key = iterate_keys[idx];
await render_row(_key, arr[_key]);
}
}
// The container can still be replaced during an awaited row render. Move the
// completed row nodes to the live replacement, then dedupe by iteration key;
// moving before deduping preserves the already-visible first row.
try {
const _post_container_node = func.runtime.ui.get_first_node(options.$container);
if (_post_container_node && !_post_container_node.isConnected) {
let _post_root = _post_container_node;
while (_post_root.parentNode && _post_root.parentNode.nodeType === 1) _post_root = _post_root.parentNode;
const _transplant_into = function ($target_node, via_label) {
const _dead_children = Array.prototype.slice.call(_post_container_node.children);
let _moved = 0;
for (let dead_i = 0; dead_i < _dead_children.length; dead_i++) {
const _dead_child = _dead_children[dead_i];
if (func.runtime.ui.get_data(func.runtime.ui._wrap_matches([_dead_child]))?.xuData?.nodeid === options.nodeP?.id) {
$target_node.appendChild(_dead_child);
_moved++;
}
}
const _seen_keys = new Set();
let _deduped = 0;
const _target_children = Array.prototype.slice.call($target_node.children);
for (let t_i = 0; t_i < _target_children.length; t_i++) {
const _t_child = _target_children[t_i];
const _t_data = func.runtime.ui.get_data(func.runtime.ui._wrap_matches([_t_child]))?.xuData;
if (!_t_data || _t_data.nodeid !== options.nodeP?.id) continue;
const _t_key = _t_data.iterate_info && typeof _t_data.iterate_info._key !== 'undefined' ? String(_t_data.iterate_info._key) : null;
if (_t_key === null) continue;
if (_seen_keys.has(_t_key)) {
func.runtime.ui.remove(_t_child);
_deduped++;
} else {
_seen_keys.add(_t_key);
}
}
console.log('[xufor-fix] transplant via ' + via_label + ': moved ' + _moved + ' rows, deduped ' + _deduped);
return _moved;
};
if (!_is_latest_run()) {
console.log('[xufor-fix] superseded run for node ' + String(options.nodeP?.id || '').slice(-10) + ' — deferred work dropped');
} else if (_post_root.tagName !== 'TEMPLATE' && _post_root.tagName !== 'TMP') {
const _post_resolved = func.runtime.render.resolve_live_xu_for_container(options.SESSION_ID, options.$container, options.nodeP?.id);
if (_post_resolved.node) {
_transplant_into(_post_resolved.node, 'post-loop/' + _post_resolved.via);
} else {
const _watch_started = Date.now();
let _last_scan = 0;
const _watcher = new MutationObserver(function () {
const _now = Date.now();
if (_now - _last_scan < 120) return;
_last_scan = _now;
try {
if (!_is_latest_run()) {
_watcher.disconnect();
console.log('[xufor-fix] watcher superseded by newer run — dropped');
return;
}
const _late = func.runtime.render.resolve_live_xu_for_container(options.SESSION_ID, options.$container, options.nodeP?.id);
if (_late.node && _late.node !== _post_container_node) {
_watcher.disconnect();
_transplant_into(_late.node, 'watcher/' + _late.via + '/+' + (_now - _watch_started) + 'ms');
}
} catch (watch_error) {
_watcher.disconnect();
console.log('[xufor-fix] watcher error', watch_error);
}
});
_watcher.observe(document.body, { childList: true, subtree: true });
setTimeout(function () {
_watcher.disconnect();
}, 12000);
}
}
}
} catch (post_loop_error) {
console.log('[xufor-fix] post-loop error', post_loop_error);
}
const _live_node = func.runtime.ui.get_first_node($live_elm);
if (_live_node) {
func.runtime.ui.remove($live_elm);
}
const _options_node = func.runtime.ui.get_first_node(options.$elm);
if (_options_node && _options_node !== _live_node) {
func.runtime.ui.remove(options.$elm);
}
return { abort: true, consume_placeholder: true };
} catch (e) {
console.error(' Iterator Arr parse error', e);
return { abort: true };
}
};
// Does the row template reference the key iterator (index)? Checked once per
// rebuild against the template node; cached per node object.
func.runtime.render.xu_for_template_reads_key = function (nodeP, iterator_key) {
if (!nodeP || !iterator_key) return false;
const cache = (func.runtime.render._xu_for_key_usage_cache = func.runtime.render._xu_for_key_usage_cache || new WeakMap());
const cached = cache.get(nodeP);
if (typeof cached !== 'undefined') return cached;
let uses = false;
try {
uses = JSON.stringify(nodeP).includes('@' + iterator_key);
} catch (e) {
uses = true; // cannot inspect: assume it does and stay on the safe path
}
cache.set(nodeP, uses);
return uses;
};
func.runtime.render.set_iterator_name = function ($elm, key, value) {
func.runtime.ui.get_data($elm).xuData[key] = value;
return {};
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Legacy xu-render flow stays isolated here so the feature-flag dispatch can stay small.
func.runtime.render.handle_legacy_xu_render = async function (options) {
const value = await func.common.get_cast_val(options.SESSION_ID, 'common fx', 'xu-render', 'bool', options.val.value);
const init_render = function () {
if (!value) {
const cloned_div = func.runtime.ui.get_first_node(options.$elm)?.cloneNode(true);
const original_data_obj = func.runtime.render.build_xu_render_original_data({
$container: cloned_div,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
$root_container: options.$root_container,
});
// Snapshot xuData while meta store still holds the entry
const snapshot_xuData = func.runtime.ui.get_data(cloned_div).xuData;
const snapshot_xuAttributes = options.nodeP.attributes || {};
const xu_ui_id = func.runtime.ui.get_attr(options.$elm, 'xu-ui-id');
// Remove original element from DOM WITHOUT deleting _meta_store.
const _elm_node = func.runtime.ui.get_first_node(options.$elm);
if (_elm_node?.remove) {
_elm_node.remove();
}
// Create XURENDER placeholder (overwrites meta store entry for this xu-ui-id)
func.runtime.render.create_xu_render_placeholder(xu_ui_id, options.$container, {
hidden: true,
xuData: snapshot_xuData,
original_data_obj,
xurender_node: cloned_div,
xuAttributes: snapshot_xuAttributes,
});
return { abort: true };
}
return {};
};
const replace_rendered_xu_node = async function (new_$div) {
const $parent = func.runtime.ui.get_parent(options.$elm);
func.runtime.ui.replace_with(options.$elm, new_$div);
if (options.from_panel) {
const xuPanelWrapper = { ...func.runtime.ui.get_data(new_$div).xuPanelWrapper };
if (func.runtime.ui.get_data($parent)) {
func.runtime.ui.get_data($parent).xuPanelWrapper = xuPanelWrapper;
}
func.runtime.ui.replace_with(options.$elm, func.runtime.ui.get_children(new_$div));
}
if (options.val.fields_arr) {
return await func.runtime.ui.refresh_xu_attributes({
SESSION_ID: options.SESSION_ID,
fields_arr: options.val.fields_arr,
jobNoP: options.val.jobNoP,
$elm_to_search: new_$div,
});
}
func.events.delete_job(options.SESSION_ID, options.jobNoP);
};
const post_render = async function () {
if (value) {
try {
const elm_node = func.runtime.ui.get_first_node(options.$elm);
if (elm_node?.tagName !== 'XURENDER' && elm_node) {
return func.events.delete_job(options.SESSION_ID, options.jobNoP);
}
const original_data_obj = func.runtime.ui.get_data(options.$elm).xuData.original_data_obj;
if (!original_data_obj) {
func.events.delete_job(options.SESSION_ID, options.jobNoP);
return { delete_job: options.jobNoP };
}
const new_$div = await func.runtime.render.render_ui_tree(
options.SESSION_ID,
options.$elm,
structuredClone(original_data_obj.nodeP),
original_data_obj.parent_infoP,
original_data_obj.paramsP,
options.jobNoP,
null,
original_data_obj.keyP,
null,
original_data_obj.parent_nodeP,
null,
original_data_obj.$root_container,
);
func.runtime.ui.get_data(new_$div).xuData.original_data_obj = original_data_obj;
func.runtime.ui.get_data(new_$div).xuData.xurender_node = func.runtime.ui.get_first_node(options.$elm)?.cloneNode(true);
func.runtime.ui.get_data(new_$div).xuAttributes = func.runtime.ui.get_data(options.$elm).xuAttributes || {};
const xu_ui_id = func.runtime.ui.get_attr(options.$elm, 'xu-ui-id');
// The placeholder→content swap must not be gated on a refresh-index lookup: when the
// (hidden) placeholder was missing from the index, the swap was silently skipped,
// leaving BOTH the freshly rendered content (appended at the container end) and the
// placeholder in the DOM — every truthy pass added another content copy, and the later
// falsy pass swapped the stale placeholder no-op, so the content never left the screen
// (un-closable create-assignment modal). Swap unconditionally, then enforce the
// one-live-element-per-xu-ui-id contract.
if (func.runtime.ui.get_data(new_$div).xuData.paramsP) {
await replace_rendered_xu_node(new_$div);
} else {
func.events.delete_job(options.SESSION_ID, options.jobNoP);
}
if (func.runtime.ui.reconcile_xu_ui_id_duplicates) {
func.runtime.ui.reconcile_xu_ui_id_duplicates(options.SESSION_ID, xu_ui_id, new_$div);
}
} catch (error) {
func.events.delete_job(options.SESSION_ID, options.jobNoP);
}
return;
}
if (func.runtime.ui.get_first_node(options.$elm)?.tagName === 'XURENDER') {
func.events.delete_job(options.SESSION_ID, options.jobNoP);
return;
}
// Resolve a stale reference before swapping in the placeholder: a prior truthy pass may
// have re-rendered the content into a different slot; replacing a detached node would
// leave the live content orphaned on screen.
{
const falsy_elm_node = func.runtime.ui.get_first_node(options.$elm);
const falsy_ui_id = func.runtime.ui.get_attr(options.$elm, 'xu-ui-id');
if (falsy_ui_id && (!falsy_elm_node || !falsy_elm_node.isConnected)) {
const $falsy_live = func.runtime?.ui?.find_xu_ui_in_root
? func.runtime.ui.find_xu_ui_in_root(options.SESSION_ID, falsy_ui_id)
: func.runtime.ui.find_by_selector(document.body, `[xu-ui-id="${falsy_ui_id}"]`);
if ($falsy_live?.length) {
options.$elm = $falsy_live;
}
}
}
const tmp_div = document.createElement('div');
const $xurender = func.runtime.ui.create_xurender(func.runtime.ui.get_attr(options.$elm, 'xu-ui-id'), tmp_div);
const elm_data = func.runtime.ui.get_data(options.$elm);
const elm_xu_data = elm_data?.xuData || {};
const elm_xu_attributes = elm_data?.xuAttributes || {};
const xurender_node_data = func.runtime.ui.get_data(elm_xu_data.xurender_node);
if (elm_xu_data.xurender_node) {
func.runtime.ui.set_data($xurender, 'xuAttributes', xurender_node_data?.xuAttributes || {});
func.runtime.ui.set_data($xurender, 'xuData', xurender_node_data?.xuData || {});
} else {
func.runtime.ui.set_data($xurender, 'xuAttributes', elm_xu_attributes);
func.runtime.ui.set_data($xurender, 'xuData', elm_xu_data);
const original_data_obj = func.runtime.render.build_xu_render_original_data({
nodeP: elm_xu_data.node_org,
paramsP: elm_xu_data.paramsP,
$container: func.runtime.ui.get_first_node(options.$elm)?.cloneNode(true),
parent_infoP: options.parent_infoP,
});
func.runtime.ui.get_data($xurender).xuData.original_data_obj = original_data_obj;
}
const elm_first_node = func.runtime.ui.get_first_node(options.$elm);
// Lifecycle: a closed screen owns nothing. Dispose datasource sessions of screens
// hosted inside the content being swapped out — leaked sessions (one set per open)
// kept their refresh listeners alive and re-rendered over every subsequent open.
const embed_hosts = elm_first_node?.querySelectorAll?.('.xu_embed_container') || [];
for (let embed_index = 0; embed_index < embed_hosts.length; embed_index++) {
const embed_screen_id = embed_hosts[embed_index].id;
if (embed_screen_id) {
try {
func.datasource.clean(options.SESSION_ID, embed_screen_id);
} catch (error) {}
}
}
const teleport_nodes = elm_first_node?.querySelectorAll?.('xu-teleport') || [];
for (let index = 0; index < teleport_nodes.length; index++) {
const val = teleport_nodes[index];
const xuTeleportData = func.runtime.ui.get_data(val).xuTeleportData || [];
for (const teleported_elm_id of xuTeleportData) {
func.runtime.ui.remove_xu_ui(teleported_elm_id);
}
}
func.runtime.ui.replace_with(options.$elm, func.runtime.ui.get_children(tmp_div));
if (func.runtime.ui.reconcile_xu_ui_id_duplicates) {
func.runtime.ui.reconcile_xu_ui_id_duplicates(options.SESSION_ID, func.runtime.ui.get_attr($xurender, 'xu-ui-id'), $xurender);
}
func.events.delete_job(options.SESSION_ID, options.jobNoP);
};
if (options.is_init) {
return init_render();
}
return await post_render();
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Modern xu-render flow stays isolated here so caching and tree reuse can evolve without the legacy path.
func.runtime.render.handle_modern_xu_render = async function (options) {
const value = await func.common.get_cast_val(options.SESSION_ID, 'common fx', 'xu-render', 'bool', options.val.value);
const has_xu_render_attribute = true;
const elm_data = func.runtime.ui.get_data(options.$elm);
const has_xu_exp_render_attribute = elm_data?.xuData?.attr_exp_info?.['xu-render'] ? true : false;
const init_render = async function () {
options.nodeP.xu_render_made = value;
if (!value) {
if (has_xu_exp_render_attribute) {
return { has_xu_exp_render_attribute, has_xu_render_attribute, xu_render_background_processing: true };
}
return { has_xu_render_attribute, abort: true };
}
return { has_xu_exp_render_attribute, has_xu_render_attribute };
};
const post_render = async function () {
const container_data = func.runtime.ui.get_data(options.$container);
if (!container_data?.xuData?.node?.children?.[options.keyP]) {
return;
}
const nodeP = container_data.xuData.node.children[options.keyP];
nodeP.xu_render_made = value;
if (value) {
try {
const exclude_fields = func.runtime.render.get_xu_render_exclude_fields(options.$elm);
const xu_render_cache_id = await func.runtime.render.get_xu_render_cache_str(
options.SESSION_ID,
options.paramsP.dsSessionP,
exclude_fields,
);
const xu_ui_id = func.runtime.ui.get_attr(options.$elm, 'xu-ui-id');
const cached_entry = UI_WORKER_OBJ?.xu_render_cache?.[xu_ui_id + xu_render_cache_id];
let found_parent_vars = false;
if (cached_entry?.$div) {
const parent_fields = func.runtime.render.get_parent_ds_field_names(options.SESSION_ID, options.paramsP.dsSessionP);
found_parent_vars = func.runtime.render.has_parent_field_dependency(cached_entry.$div, parent_fields, cached_entry?.dependency_fields);
}
let new_$div = !found_parent_vars && cached_entry?.$div ? func.runtime.ui.get_first_node(cached_entry.$div)?.cloneNode(true) : null;
if (!new_$div || found_parent_vars) {
func.runtime.render.cache_xu_render(xu_ui_id + xu_render_cache_id, { paramsP: options.paramsP });
nodeP.xu_render_xu_ui_id = xu_ui_id;
nodeP.xu_render_cache_id = xu_render_cache_id;
new_$div = await func.runtime.render.render_ui_tree(
options.SESSION_ID,
options.$container,
nodeP,
options.parent_infoP,
options.paramsP,
options.jobNoP,
null,
options.keyP,
null,
options.parent_nodeP,
null,
options.$root_container,
);
const _$div = func.runtime.ui.get_first_node(new_$div)?.cloneNode(true);
func.runtime.render.cache_xu_render(xu_ui_id + xu_render_cache_id, {
...UI_WORKER_OBJ.xu_render_cache[xu_ui_id + xu_render_cache_id],
$div: _$div,
data: func.runtime.ui.get_data(_$div),
});
}
func.runtime.render.insert_ordered_child(options.$container, new_$div, options.keyP);
// Remove the XURENDER placeholder now that the real content has been inserted.
if (func.runtime.ui.get_first_node(options.$elm)?.tagName === 'XURENDER') {
func.runtime.ui.remove(options.$elm);
}
// Enforce one live element per xu-ui-id: a stale options.$elm (placeholder replaced or
// moved since this job was queued) leaves the old copy behind — content duplicates.
if (func.runtime.ui.reconcile_xu_ui_id_duplicates) {
func.runtime.ui.reconcile_xu_ui_id_duplicates(options.SESSION_ID, xu_ui_id, new_$div);
}
} catch (error) {
func.events.delete_job(options.SESSION_ID, options.jobNoP);
}
return;
}
// Cancel any in-flight child jobs before removing the element.
// This prevents orphaned jobs (e.g. a child panel's long-running on_load delay)
// from blocking future scheduler passes after the parent element is gone.
if (func.UI?.worker?.cancel_child_in_flight_jobs) {
func.UI.worker.cancel_child_in_flight_jobs(options.$elm);
}
const xu_ui_id = func.runtime.ui.get_attr(options.$elm, 'xu-ui-id');
const exclude_fields = func.runtime.render.get_xu_render_exclude_fields(options.$elm);
const cache_str = await func.runtime.render.get_xu_render_cache_str(
options.SESSION_ID,
options.paramsP.dsSessionP,
exclude_fields,
);
const _$div = func.runtime.ui.get_first_node(options.$elm)?.cloneNode(true);
func.runtime.render.cache_xu_render(xu_ui_id + cache_str, { $div: _$div, data: func.runtime.ui.get_data(_$div), paramsP: options.paramsP });
// Lifecycle: dispose datasource sessions of screens hosted inside the removed content
// (same contract as the legacy falsy path — a closed screen owns nothing).
const closing_node = func.runtime.ui.get_first_node(options.$elm);
const embed_hosts = closing_node?.querySelectorAll?.('.xu_embed_container') || [];
for (let embed_index = 0; embed_index < embed_hosts.length; embed_index++) {
const embed_screen_id = embed_hosts[embed_index].id;
if (embed_screen_id) {
try {
func.datasource.clean(options.SESSION_ID, embed_screen_id);
} catch (error) {}
}
}
func.runtime.ui.remove(options.$elm);
// Enforce one live element per xu-ui-id: if options.$elm went stale (content re-rendered
// or moved since this job was queued), the live copy would survive the remove above and
// stay on screen even though its gate is now falsy (un-closable modal).
if (func.runtime.ui.reconcile_xu_ui_id_duplicates) {
func.runtime.ui.reconcile_xu_ui_id_duplicates(options.SESSION_ID, xu_ui_id, null);
}
func.events.delete_job(options.SESSION_ID, options.jobNoP);
};
if (options.is_init) {
return await init_render();
}
return await post_render();
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only xu-render dispatch lives here so legacy and modern render paths can stay isolated.
func.runtime.render.handle_xu_render = async function (options) {
if (glb.new_xu_render) {
return await func.runtime.render.handle_modern_xu_render(options);
}
return await func.runtime.render.handle_legacy_xu_render(options);
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only DOM-oriented xu handler helpers live here so handler dispatch can stay focused.
func.runtime.render.apply_expression_attribute = async function (options) {
const new_val = {
key: options.key,
value: func.runtime.render.fix_val_defaults(options.key, options.exp_ret.result),
};
if (func.runtime.render.is_xu_tag(options.nodeP.tagName)) {
if (options.tag_fx?.[options.nodeP.tagName]?.[new_val.key]) {
return await options.tag_fx[options.nodeP.tagName][new_val.key](options.$elm, new_val);
}
if (options.nodeP.tagName === 'xu-panel' && !new_val.key.startsWith('xu-')) {
return {};
}
console.warn(`attribute ${new_val.key} not found for ${options.nodeP.tagName}`);
return {};
}
if (!func.runtime.ui.get_data(options.$elm)?.xuData) {
return {};
}
func.runtime.render.record_attribute_stat(options.$elm, new_val.key, new_val.value);
if (typeof new_val.value === 'undefined' || new_val.value === null) {
func.runtime.ui.remove_attr(options.$elm, new_val.key);
return {};
}
if (glb.solid_attributes.includes(new_val.key) && !new_val.value) {
func.runtime.ui.remove_attr(options.$elm, new_val.key);
return {};
}
if (new_val.key.substr(0, 2) === 'xu') {
return await options.common_fx[new_val.key](options.$elm, new_val);
}
if (new_val.key === 'class') {
return func.runtime.render.apply_expression_class(options.$elm, new_val.value);
}
const existing_value = func.runtime.ui.get_attr(options.$elm, new_val.key) || '';
func.runtime.ui.set_attr(options.$elm, new_val.key, existing_value + new_val.value);
return {};
};
func.runtime.render.apply_expression_class = function ($elm, new_class_value) {
const xuData = func.runtime.ui.get_data($elm)?.xuData;
const old_exp_classes = xuData?._exp_class_cache || '';
if (old_exp_classes) {
const old_classes = old_exp_classes.split(/\s+/).filter(Boolean);
for (let i = 0; i < old_classes.length; i++) {
func.runtime.ui.remove_class($elm, old_classes[i]);
}
}
const new_value = (new_class_value || '').toString();
const new_classes = new_value.split(/\s+/).filter(Boolean);
for (let i = 0; i < new_classes.length; i++) {
func.runtime.ui.add_class($elm, new_classes[i]);
}
if (xuData) {
xuData._exp_class_cache = new_value;
}
return {};
};
func.runtime.render.apply_visibility = function ($elm, value) {
if (value) {
func.runtime.ui.show($elm);
return;
}
func.runtime.ui.hide($elm);
};
func.runtime.render.apply_dom_content = function ($elm, value, mode = 'html') {
if (mode === 'text') {
func.runtime.ui.set_text($elm, value);
return;
}
func.runtime.ui.set_html($elm, value);
};
func.runtime.render.run_inline_script = function ($elm, script_body) {
const checkExist = setInterval(async function () {
const node = func.runtime.ui.get_first_node($elm);
if (node && node.offsetParent !== null) {
try {
// A function-body inline script — "(elm) => {…}", "elm => {…}", "function(el){…}" — must be
// CALLED with the node, not merely defined; otherwise wrapping it as `async(el)=>{ (elm)=>{…} }`
// just declares the arrow and never runs it (e.g. the gallery's `new Swiper(elm,{…})`). Detect a
// function body, eval it as an expression, and invoke it with the node. Statement bodies keep the
// original wrap-and-run path unchanged.
const trimmed = `${script_body || ''}`.trim();
const is_function_body =
/^(async\s+)?(\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>/.test(trimmed) || /^(async\s+)?function[\s*(]/.test(trimmed);
if (is_function_body) {
const f = eval(`(${script_body})`);
if (typeof f === 'function') await f(node);
} else {
const fn = `async (el)=>{${script_body} };`;
const res = eval(fn);
await res(node);
}
} catch (e) {
try {
eval(script_body);
} catch (e2) {
console.error('[xuda-runtime] inline script error', e2);
}
} finally {
clearInterval(checkExist);
}
}
}, 100);
};
// --- Legacy jQuery `$` global shim ------------------------------------------
// Some legacy apps call jQuery `$` from INLINE xu-script bodies, which
// run_inline_script eval's directly. Per-program shims are block
// scoped and invisible to that eval, so install the pure-JS `$` shim as a global
// once here (browser-only module, parsed before any inline script runs).
// Guarded: never clobber a real jQuery (a superset) or a per-program const $.
(function install_legacy_jq_shim() {
const host = typeof window !== 'undefined' ? window : typeof globalThis !== 'undefined' ? globalThis : null;
if (!host || host.$) return;
const $ = function() {
const proto = {};
const make = (els) => {
const col = Object.create(proto);
let n = 0;
for (const el of els) if (el != null) col[n++] = el;
col.length = n;
col.__col = true;
return col;
};
const sanitizeSel = (s) => s.replace(/\[\s*([-\w]+)\s*([~|^$*]?=)\s*([^\]'"]+?)\s*\]/g, (m, a, op, v) => `[${a}${op}"${v}"]`);
const qsa = (rootEl, sel) => {
try {
return Array.prototype.slice.call(rootEl.querySelectorAll(sel));
} catch (e) {
try {
return Array.prototype.slice.call(rootEl.querySelectorAll(sanitizeSel(sel)));
} catch (_) {
return [];
}
}
};
const toNodes = (arg) => {
if (arg == null) return [];
if (arg.__col) return Array.prototype.slice.call(arg, 0, arg.length);
if (arg instanceof Node || arg === window || arg === document) return [arg];
if (typeof arg === "string") {
const s = arg.trim();
if (s[0] === "<") {
const t = document.createElement("template");
t.innerHTML = s;
return Array.prototype.slice.call(t.content.childNodes);
}
return qsa(document, arg);
}
if (typeof arg.length === "number") return Array.prototype.slice.call(arg);
return [arg];
};
const root = (arg) => make(toNodes(arg));
const parseHTML = (html) => {
const t = document.createElement("template");
t.innerHTML = html;
return Array.prototype.slice.call(t.content.childNodes);
};
const contentNodes = (content) => {
if (content == null) return [];
if (content.__col) return Array.prototype.slice.call(content, 0, content.length);
if (content instanceof Node) return [content];
if (typeof content === "string") return parseHTML(content);
if (typeof content.length === "number") return Array.prototype.slice.call(content);
return [content];
};
const each = function(cb) {
for (let i = 0; i < this.length; i++) if (cb.call(this[i], i, this[i]) === false) break;
return this;
};
const eventStore = (el) => el.__jqEv || (el.__jqEv = {});
Object.assign(proto, {
each,
get(i) {
return i == null ? Array.prototype.slice.call(this, 0, this.length) : this[i < 0 ? this.length + i : i];
},
eq(i) {
return make([this.get(i)].filter(Boolean));
},
first() {
return this.eq(0);
},
last() {
return this.eq(-1);
},
add(other) {
return make(toNodes(this).concat(toNodes(other)));
},
filter(sel) {
const out = [];
each.call(this, function(i, el) {
if (typeof sel === "function") {
if (sel.call(el, i, el)) out.push(el);
} else if (el.matches && el.matches(sel)) out.push(el);
});
return make(out);
},
find(sel) {
const out = [];
each.call(this, (i, el) => {
if (el.querySelectorAll) out.push.apply(out, qsa(el, sel));
});
return make(out);
},
parent() {
const seen = /* @__PURE__ */ new Set(), out = [];
each.call(this, (i, el) => {
const p = el.parentNode;
if (p && !seen.has(p)) {
seen.add(p);
out.push(p);
}
});
return make(out);
},
children(sel) {
const out = [];
each.call(this, (i, el) => {
for (const c of el.children || []) if (!sel || c.matches && c.matches(sel)) out.push(c);
});
return make(out);
},
contents() {
const out = [];
each.call(this, (i, el) => {
for (const c of el.childNodes || []) out.push(c);
});
return make(out);
},
attr(name, val) {
if (val === void 0 && typeof name === "string") return this[0] ? this[0].getAttribute(name) : void 0;
return each.call(this, (i, el) => {
if (typeof name === "object") {
for (const k in name) el.setAttribute(k, name[k]);
} else el.setAttribute(name, val);
});
},
removeAttr(name) {
return each.call(this, (i, el) => el.removeAttribute(name));
},
addClass(c) {
const cs = String(c).split(/\s+/).filter(Boolean);
return each.call(this, (i, el) => el.classList.add(...cs));
},
removeClass(c) {
const cs = String(c).split(/\s+/).filter(Boolean);
return each.call(this, (i, el) => el.classList.remove(...cs));
},
hasClass(c) {
return this[0] ? this[0].classList.contains(c) : false;
},
css(name, val) {
if (typeof name === "object") return each.call(this, (i, el) => {
for (const k in name) setStyle(el, k, name[k]);
});
if (val === void 0) {
const el = this[0];
return el ? getComputedStyle(el).getPropertyValue(toKebab(name)) : void 0;
}
return each.call(this, (i, el) => setStyle(el, name, val));
},
html(v) {
if (v === void 0) return this[0] ? this[0].innerHTML : void 0;
return each.call(this, (i, el) => {
el.innerHTML = v;
});
},
text(v) {
if (v === void 0) return this[0] ? this[0].textContent : void 0;
return each.call(this, (i, el) => {
el.textContent = v;
});
},
append(content) {
const nodes = contentNodes(content);
return each.call(this, (i, el) => {
for (const n of nodes) el.appendChild(i === this.length - 1 ? n : n.cloneNode(true));
});
},
prepend(content) {
const nodes = contentNodes(content);
return each.call(this, (i, el) => {
const arr = i === this.length - 1 ? nodes : nodes.map((n) => n.cloneNode(true));
for (let k = arr.length - 1; k >= 0; k--) el.insertBefore(arr[k], el.firstChild);
});
},
after(content) {
const nodes = contentNodes(content);
return each.call(this, (i, el) => {
if (!el.parentNode) return;
const ref = el.nextSibling;
for (const n of nodes) el.parentNode.insertBefore(i === this.length - 1 ? n : n.cloneNode(true), ref);
});
},
before(content) {
const nodes = contentNodes(content);
return each.call(this, (i, el) => {
if (!el.parentNode) return;
for (const n of nodes) el.parentNode.insertBefore(i === this.length - 1 ? n : n.cloneNode(true), el);
});
},
insertBefore(target) {
const ref = toNodes(target)[0];
if (ref && ref.parentNode) each.call(this, (i, el) => ref.parentNode.insertBefore(el, ref));
return this;
},
has(sel) {
const out = [];
each.call(this, (i, el) => {
if (el.querySelector && el.querySelector(sel)) out.push(el);
});
return make(out);
},
slice(a, b) {
return make(Array.prototype.slice.call(this, 0, this.length).slice(a, b));
},
// jQuery .map(): returns a real array (so vue-grid etc. can use it directly),
// but also carries .toArray()/.get() so `$(...).map(...).toArray()` chains work.
map(cb) {
const out = [];
for (let i = 0; i < this.length; i++) {
const r = cb.call(this[i], i, this[i]);
if (r != null) {
if (Array.isArray(r)) out.push.apply(out, r);
else out.push(r);
}
}
Object.defineProperty(out, "toArray", { value: function() {
return this;
}, enumerable: false });
Object.defineProperty(out, "get", { value: function(i) {
return i == null ? this : this[i];
}, enumerable: false });
return out;
},
toArray() {
return Array.prototype.slice.call(this, 0, this.length);
},
not(sel) {
const out = [];
each.call(this, function(i, el) {
if (typeof sel === "function") {
if (!sel.call(el, i, el)) out.push(el);
} else if (!(el.matches && el.matches(sel))) out.push(el);
});
return make(out);
},
empty() {
return each.call(this, (i, el) => {
el.innerHTML = "";
});
},
remove() {
return each.call(this, (i, el) => {
if (el.parentNode) el.parentNode.removeChild(el);
});
},
replaceWith(content) {
return each.call(this, (i, el) => {
const nodes = contentNodes(content);
if (el.replaceWith) el.replaceWith(...nodes);
});
},
unwrap() {
const parents = /* @__PURE__ */ new Set();
each.call(this, (i, el) => {
if (el.parentNode) parents.add(el.parentNode);
});
parents.forEach((p) => {
if (p.parentNode) p.replaceWith(...Array.prototype.slice.call(p.childNodes));
});
return this;
},
hide() {
return each.call(this, (i, el) => {
el.style.display = "none";
});
},
show() {
return each.call(this, (i, el) => {
el.style.display = "";
});
},
width() {
const el = this[0];
return el ? el.getBoundingClientRect().width : 0;
},
position() {
const el = this[0];
return el ? { top: el.offsetTop, left: el.offsetLeft } : { top: 0, left: 0 };
},
// events: native add/removeEventListener; custom events via CustomEvent.detail
on(type, handler) {
return each.call(this, (i, el) => {
const wrap = (e) => handler.call(el, e, e.detail);
const store = eventStore(el);
(store[type] || (store[type] = [])).push({ handler, wrap });
el.addEventListener(type, wrap);
});
},
off(type, handler) {
return each.call(this, (i, el) => {
const store = el.__jqEv;
if (!store || !store[type]) return;
store[type] = store[type].filter((rec) => {
if (handler && rec.handler !== handler) return true;
el.removeEventListener(type, rec.wrap);
return false;
});
});
},
trigger(type, detail) {
return each.call(this, (i, el) => el.dispatchEvent(new CustomEvent(type, { detail, bubbles: true })));
}
});
proto.unbind = proto.off;
["click", "mouseup", "mousedown", "change", "keyup", "keydown", "blur", "focus"].forEach((ev) => {
proto[ev] = function(handler) {
return handler ? this.on(ev, handler) : this.trigger(ev);
};
});
function toKebab(s) {
return s.indexOf("-") > -1 ? s : s.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
}
function setStyle(el, name, val) {
if (name.indexOf("-") > -1) el.style.setProperty(name, val);
else el.style[name] = val;
}
root.each = function(coll, cb) {
if (coll == null) return coll;
if (typeof coll.length === "number" && typeof coll !== "function" && coll.nodeType === void 0) {
for (let i = 0; i < coll.length; i++) if (cb.call(coll[i], i, coll[i]) === false) break;
} else {
for (const k in coll) if (cb.call(coll[k], k, coll[k]) === false) break;
}
return coll;
};
proto.data = function (key, val) {
if (arguments.length >= 2) {
for (let i = 0; i < this.length; i++) (this[i].__xuData || (this[i].__xuData = {}))[key] = val;
return this;
}
const el = this[0];
if (!el) return arguments.length === 1 ? undefined : {};
const store = el.__xuData || (el.__xuData = {});
if (arguments.length === 1) {
if (key in store) return store[key];
return el.dataset ? el.dataset[key] : undefined;
}
const out = {};
if (el.dataset) for (const k in el.dataset) out[k] = el.dataset[k];
for (const k in store) out[k] = store[k];
return out;
};
proto.siblings = function (sel) {
const out = [];
for (let i = 0; i < this.length; i++) {
const el = this[i];
if (!el || !el.parentNode) continue;
for (const sib of el.parentNode.children) {
if (sib !== el && (!sel || (sib.matches && sib.matches(sel))) && out.indexOf(sib) === -1) out.push(sib);
}
}
return make(out);
};
proto.draggabilly = function (opts) {
const Lib = (typeof window !== "undefined" && window.Draggabilly) || (typeof globalThis !== "undefined" && globalThis.Draggabilly);
for (let i = 0; i < this.length; i++) {
const el = this[i];
const store = el.__xuData || (el.__xuData = {});
if (opts === "destroy") {
if (store.draggabilly && store.draggabilly.destroy) store.draggabilly.destroy();
delete store.draggabilly;
continue;
}
if (!Lib) { if (!proto.__dbWarned) { proto.__dbWarned = true; console.warn("[xuda-runtime] Draggabilly not loaded; toolbar drag disabled"); } continue; }
store.draggabilly = new Lib(el, opts || {});
}
return this;
};
proto.draggable = function () {
if (!proto.__duiWarned) { proto.__duiWarned = true; console.warn("[xuda-runtime] jQuery-UI draggable not loaded; drag disabled"); }
return this;
};
return root;
}();
host.$ = $;
if (typeof globalThis !== 'undefined') globalThis.$ = $;
})();
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only node-related xu handlers live here so special-node rendering can stay focused.
func.runtime.render.handle_xu_panel_program = async function (options) {
let ret = {};
const _session = SESSION_OBJ[options.SESSION_ID];
const _ds = _session.DS_GLB[options.paramsP.dsSessionP];
const _refreshed_ds = _session.DS_GLB[options.refreshed_ds];
const render_panel_init = async function () {
const prog_id = options.val.value?.prog || options.val.value;
let create_new_ds = true;
if (options.refreshed_ds && _refreshed_ds?.prog_id === prog_id) {
create_new_ds = false;
}
const params_obj = await options.get_params_obj_new(options.SESSION_ID, prog_id, options.nodeP, options.paramsP.dsSessionP);
const ret_panel = await func.runtime.ui.init_screen({
SESSION_ID: options.SESSION_ID,
prog_id,
sourceScreenP: options.paramsP.screenId,
callingDataSource_objP: _ds,
$callingContainerP: options.$elm,
triggerIdP: null,
rowIdP: _ds.currentRecordId,
jobNoP: null,
is_panelP: true,
parameters_obj_inP: params_obj.params_res,
source_functionP: 'initXu_panel',
call_screen_propertiesP: undefined,
refreshed_ds: create_new_ds ? null : options.refreshed_ds,
parameters_raw_obj: params_obj.params_raw,
});
ret = { $new_div: ret_panel };
const container_data = func.runtime.ui.get_data(options.$container);
if (container_data?.xuData) {
container_data.xuData.xuPanelProps = func.runtime.ui.get_data(options.$elm)?.xuAttributes;
container_data.xuData.xuPanelData = func.runtime.ui.get_data(ret_panel);
}
return ret;
};
const render_panel_alter = async function () {
const program = options.val.value?.prog || options.val.value;
const $wrapper = document.createElement('div');
const $div = await func.runtime.ui.create_container({
SESSION_ID: options.SESSION_ID,
$root_container: options.$root_container,
nodeP: options.nodeP,
$container: options.$container,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
jobNoP: options.jobNoP,
keyP: options.keyP,
treeP: options.treeP,
parent_nodeP: options.parent_nodeP,
prop: options.nodeP.attributes,
$appendToP: $wrapper,
attr_str: '',
});
const params_obj = await options.get_params_obj_new(options.SESSION_ID, program, options.nodeP, options.paramsP.dsSessionP);
const ret_init = await func.runtime.ui.init_screen({
SESSION_ID: options.SESSION_ID,
prog_id: program,
sourceScreenP: options.paramsP.screenId,
callingDataSource_objP: _ds,
$callingContainerP: $div,
triggerIdP: null,
rowIdP: _ds.currentRecordId,
jobNoP: options.jobNoP,
is_panelP: true,
parameters_obj_inP: params_obj.params_res,
source_functionP: 'alterXu_panel',
call_screen_propertiesP: undefined,
refreshed_ds: undefined,
parameters_raw_obj: params_obj.params_raw,
});
ret = {
$new_div: ret_init,
abort: true,
};
await func.runtime.ui.panel_post_render_handler({
SESSION_ID: options.SESSION_ID,
$container: options.$elm,
$wrapper: ret.$new_div,
nodeP: options.nodeP,
$panel_div: $div,
jobNoP: options.jobNoP,
});
return ret;
};
if (!options.val.value) {
if (options.is_init) {
options.val.value = '_empty_panel_program';
} else {
return { abort: true };
}
}
if (options.is_init) {
return await render_panel_init();
}
return await render_panel_alter();
};
func.runtime.render.handle_xu_teleport = async function (options) {
if (glb.new_xu_render) {
return {};
}
if (!options.val.value) {
return { abort: true };
}
const $parent = func.runtime.ui.get_parent(options.$elm);
const parent_data = func.runtime.ui.get_data($parent);
if (parent_data?.xuData) {
func.runtime.ui.set_data($parent, 'xuTeleportData', []);
const parent_xu_ui_id = func.runtime.ui.get_attr($parent, 'xu-ui-id');
for (let index = 0; index < options.nodeP.children.length; index++) {
const node = options.nodeP.children[index];
const $to_container = document.querySelector(options.val.value);
if (!$to_container) {
await func.utils.report_issue(options.SESSION_ID, {
code: 'RUN_MSG_GUI_010',
source: 'xu-teleport',
message: `container ${options.val.value} for xuTeleportData not found`,
type: 'E',
details: {
target: options.val.value,
},
});
return { abort: true };
}
const $teleport_elm = await func.runtime.render.render_ui_tree(
options.SESSION_ID,
$to_container,
node,
options.parent_infoP,
options.paramsP,
options.jobNoP,
options.is_skeleton,
index,
null,
node,
null,
options.$root_container,
);
parent_data.xuTeleportData.push(func.runtime.ui.get_attr($teleport_elm, 'xu-ui-id'));
func.runtime.ui.set_attr($teleport_elm, 'xu-teleport-parent-id', parent_xu_ui_id);
}
func.runtime.ui.remove(options.$elm);
return { abort: true };
}
func.runtime.ui.set_data(options.$elm, 'xuTeleportData', []);
func.runtime.ui.set_attr(options.$elm, 'hidden', true);
for (let index = 0; index < options.nodeP.children.length; index++) {
const node = options.nodeP.children[index];
const $to_container = document.querySelector(options.val.value);
if (!$to_container) {
await func.utils.report_issue(options.SESSION_ID, {
code: 'RUN_MSG_GUI_010',
source: 'xu-teleport',
message: `container ${options.val.value} for xuTeleportData not found`,
type: 'E',
details: {
target: options.val.value,
},
});
return { abort: true };
}
const $teleport_elm = await func.runtime.render.render_ui_tree(
options.SESSION_ID,
$to_container,
node,
options.parent_infoP,
options.paramsP,
options.jobNoP,
options.is_skeleton,
index,
null,
node,
null,
options.$root_container,
);
func.runtime.ui.get_data(options.$elm).xuTeleportData.push(func.runtime.ui.get_attr($teleport_elm, 'xu-ui-id'));
func.runtime.ui.set_attr($teleport_elm, 'xu-teleport-parent-id', func.runtime.ui.get_attr(options.$elm, 'xu-ui-id'));
}
return { abort: true };
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only common xu handler factories live here so registry composition can stay small.
func.runtime.render.build_base_xu_handlers = function (options, _ds) {
const decode_html_entities = function (value) {
if (typeof value !== 'string' || value.indexOf('&') === -1) {
return value;
}
if (typeof document !== 'undefined') {
const textarea = document.createElement('textarea');
textarea.innerHTML = value;
return textarea.value;
}
return value
.replaceAll('"', '"')
.replaceAll('"', '"')
.replaceAll(''', "'")
.replaceAll(''', "'")
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('&', '&');
};
const parse_object_value = function (attr_name, val, shape = 'object') {
let parsed_value = val?.value;
if (typeof parsed_value === 'string') {
const decoded_value = decode_html_entities(parsed_value);
const trimmed_value = decoded_value.trim();
const wrapped_candidate =
trimmed_value.startsWith('(') && trimmed_value.endsWith(')')
? trimmed_value.slice(1, -1).trim()
: trimmed_value;
const looks_like_wrapped_shape =
(shape === 'object' && wrapped_candidate.startsWith('{') && wrapped_candidate.endsWith('}')) ||
(shape === 'array' && wrapped_candidate.startsWith('[') && wrapped_candidate.endsWith(']'));
try {
parsed_value = JSON5.parse(looks_like_wrapped_shape ? wrapped_candidate : trimmed_value);
} catch (error) {
throw func.runtime.render.build_xu_runtime_error(
{ ...options, xu_func: attr_name, val: { key: attr_name, value: val?.value } },
error,
`${attr_name} has invalid ${shape} syntax`,
);
}
}
const valid =
shape === 'array'
? Array.isArray(parsed_value)
: typeof parsed_value === 'object' && parsed_value !== null && !Array.isArray(parsed_value);
if (!valid) {
throw func.runtime.render.build_xu_runtime_error(
{ ...options, xu_func: attr_name, val: { key: attr_name, value: val?.value } },
null,
`${attr_name} expects a ${shape} value`,
);
}
return parsed_value;
};
return {
'xu-attrs': async function ($elm, val) {
if (!val.value) return {};
const attrs_obj = parse_object_value('xu-attrs', val, 'object');
const attr_keys = Object.keys(attrs_obj);
for (let index = 0; index < attr_keys.length; index++) {
const attr_key = attr_keys[index];
options.nodeP.attributes[attr_key] = attrs_obj[attr_key];
}
return {};
},
'xu-ref': async function ($elm, val, dsSession) {
return await func.runtime.render.handle_xu_ref({
SESSION_ID: options.SESSION_ID,
$elm,
paramsP: options.paramsP,
val,
dsSession,
});
},
'xu-bind': async function ($elm, val) {
return await func.runtime.render.handle_xu_bind({
SESSION_ID: options.SESSION_ID,
is_skeleton: options.is_skeleton,
$elm,
paramsP: options.paramsP,
val,
ds: _ds,
});
},
'xu-render': async function ($elm, val, from_panel) {
return await func.runtime.render.handle_xu_render({
SESSION_ID: options.SESSION_ID,
$elm,
$container: options.$container,
$root_container: options.$root_container,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
val,
is_init: options.is_init,
from_panel,
});
},
'xu-show': async function ($elm, val) {
const value = await func.common.get_cast_val(options.SESSION_ID, 'common fx', 'xu-show', 'bool', val.value);
func.runtime.render.apply_visibility($elm, value);
return {};
},
'xu-content': async function ($elm, val) {
try {
func.runtime.render.apply_dom_content($elm, val.value, 'html');
} catch (error) {
console.warn(error);
}
return;
},
'xu-text': async function ($elm, val) {
try {
func.runtime.render.apply_dom_content($elm, val.value, 'text');
} catch (error) {
console.warn(error);
}
return;
},
'xu-html': async function ($elm, val) {
try {
func.runtime.render.apply_dom_content($elm, val.value, 'html');
} catch (error) {
console.warn(error);
}
return;
},
'xu-for': async function ($elm, data) {
return await func.runtime.render.handle_xu_for({
SESSION_ID: options.SESSION_ID,
$elm,
$live_elm: options.$live_elm || $elm,
$container: options.$container,
$root_container: options.$root_container,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
data,
});
},
'xu-for-key': async function ($elm, val) {
return func.runtime.render.set_iterator_name($elm, 'iterator_key', val.value);
},
'xu-for-val': async function ($elm, val) {
return func.runtime.render.set_iterator_name($elm, 'iterator_val', val.value);
},
'xu-class': async function ($elm, val) {
return await func.runtime.render.apply_xu_class({
SESSION_ID: options.SESSION_ID,
$elm,
paramsP: options.paramsP,
val,
});
},
'xu-on': async function ($elm, val) {
func.runtime.render.bind_xu_event({
SESSION_ID: options.SESSION_ID,
paramsP: options.paramsP,
$elm,
val,
});
return {};
},
'xu-script': async function ($elm, val) {
func.runtime.render.run_inline_script($elm, val.value);
return {};
},
'xu-style-global': async function ($elm, val) {
func.runtime.render.append_style_tag(val.value);
return {};
},
'xu-style': async function ($elm, val) {
const newCSSString = func.runtime.render.scope_css_to_xu_ui($elm, val.value);
func.runtime.render.append_style_tag(newCSSString);
return {};
},
'xu-cdn': async function ($elm, val) {
// xu-cdn value can be an array [{src,type},...] or an object map; both
// iterate the same way via Object.keys. Accept either (was 'object' only,
// which threw "expects a object value" on the array form).
let resources_obj;
try {
resources_obj = parse_object_value('xu-cdn', val, 'array');
} catch (_) {
resources_obj = parse_object_value('xu-cdn', val, 'object');
}
const resource_keys = Object.keys(resources_obj || {});
for (let index = 0; index < resource_keys.length; index++) {
const resource = resources_obj[resource_keys[index]];
await func.runtime.resources.load_cdn(options.SESSION_ID, resource);
}
return {};
},
'xu-ui-plugin': async function ($elm, val) {
const plugins_obj = parse_object_value('xu-ui-plugin', val, 'object');
const plugin_names = Object.keys(plugins_obj);
for (let index = 0; index < plugin_names.length; index++) {
const plugin_name = plugin_names[index];
const value = plugins_obj[plugin_name];
await func.runtime.resources.run_ui_plugin(options.SESSION_ID, options.paramsP, $elm, plugin_name, value);
}
return {};
},
'xu-store': async function ($elm, val) {
try {
const fields_obj = parse_object_value('xu-store', val, 'object');
const field_ids = Object.keys(fields_obj);
for (let index = 0; index < field_ids.length; index++) {
const field_id = field_ids[index];
func.datasource.add_dynamic_field_to_ds(options.SESSION_ID, options.paramsP.dsSessionP, field_id, fields_obj[field_id]);
}
} catch (err) {
throw err;
}
return {};
},
'xu-viewport': async function () {
return {};
},
};
};
func.runtime.render.build_expression_xu_handler = function (options, common_fx, tag_fx) {
return async function ($elm, val) {
if (!SESSION_OBJ[options.SESSION_ID].DS_GLB[options.paramsP.dsSessionP]) return {};
const exp = val.value === null ? true : val.value;
const xuData = func.runtime.ui.get_data($elm)?.xuData;
const exp_ret = await func.expression.get(
options.SESSION_ID,
exp,
options.paramsP.dsSessionP,
'UI Attr EXP',
SESSION_OBJ[options.SESSION_ID].DS_GLB[options.paramsP.dsSessionP].currentRecordId,
null,
null,
null,
null,
null,
xuData?.iterate_info,
);
return await func.runtime.render.apply_expression_attribute({
$elm,
key: val.key,
exp_ret,
nodeP: options.nodeP,
tag_fx,
common_fx,
});
};
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only tag-specific xu handler factories live here so special-node policy stays isolated.
func.runtime.render.build_tag_xu_handlers = function (options, common_fx) {
return {
'xu-panel': {
program: async function ($elm, val) {
return await func.runtime.render.handle_xu_panel_program({
SESSION_ID: options.SESSION_ID,
$elm,
$container: options.$container,
$root_container: options.$root_container,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
val,
is_init: options.is_init,
refreshed_ds: options.refreshed_ds,
get_params_obj_new: options.get_params_obj_new,
});
},
'xu-render': async function ($elm, val) {
return await common_fx['xu-render']($elm, val, true);
},
'xu-ref': async function ($elm, val) {
if (!val.value) return {};
return await common_fx['xu-ref'](options.$container, val, func.runtime.ui.get_data(options.$container)?.xuData?.xuPanelData?.xuData?.paramsP?.dsSessionP);
},
},
'xu-teleport': {
to: async function ($elm, val) {
return await func.runtime.render.handle_xu_teleport({
SESSION_ID: options.SESSION_ID,
$elm,
$root_container: options.$root_container,
nodeP: options.nodeP,
parent_infoP: options.parent_infoP,
paramsP: options.paramsP,
jobNoP: options.jobNoP,
is_skeleton: options.is_skeleton,
val,
});
},
'xu-render': async function ($elm, val) {
return await common_fx['xu-render']($elm, val, true);
},
'xu-show': async function ($elm, val) {
return await common_fx['xu-show']($elm, val, true);
},
},
};
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only xu handler registry composition lives here so execute_xu_function can ask for one ready object.
func.runtime.render.build_common_xu_handlers = function (options, _ds, tag_fx) {
const common_fx = func.runtime.render.build_base_xu_handlers(options, _ds);
common_fx['xu-exp'] = func.runtime.render.build_expression_xu_handler(options, common_fx, tag_fx);
return common_fx;
};
func.runtime.render.build_xu_handlers = function (options, _ds) {
const common_fx = func.runtime.render.build_base_xu_handlers(options, _ds);
const tag_fx = func.runtime.render.build_tag_xu_handlers(options, common_fx);
common_fx['xu-exp'] = func.runtime.render.build_expression_xu_handler(options, common_fx, tag_fx);
return { common_fx, tag_fx };
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Browser-only xu-attribute dispatch lives here so the attribute phase engine can stay focused.
func.runtime.render.build_xu_runtime_error = function (options, error, fallback_message) {
const raw_message = error?.message || error || fallback_message || 'Unknown runtime error';
const err = error instanceof Error ? error : new Error(raw_message);
err.xu_func = options?.xu_func;
err.node_tag = options?.nodeP?.tagName;
err.raw_value = options?.val?.value;
err.ui_id = func.runtime?.ui?.get_attr ? func.runtime.ui.get_attr(options?.$elm, 'xu-ui-id') : null;
return err;
};
func.runtime.render.report_xu_runtime_error = async function (options, error, fallback_message) {
const err = func.runtime.render.build_xu_runtime_error(options, error, fallback_message);
const attr_name = options?.val?.key || options?.xu_func || 'xu-*';
const tag_name = options?.nodeP?.tagName || 'unknown';
const raw_value = typeof err.raw_value === 'string' ? err.raw_value : JSON.stringify(err.raw_value);
const message = [`${attr_name} failed on <${tag_name}>`, err.message];
if (raw_value) {
message.push(`Value: ${raw_value}`);
}
if (err.ui_id) {
message.push(`xu-ui-id: ${err.ui_id}`);
}
if (func.utils?.report_issue) {
await func.utils.report_issue(options?.SESSION_ID, {
source: 'Slim runtime',
message: message.join(' | '),
type: 'E',
err,
details: {
xu_func: options?.xu_func,
attr_name,
tag_name,
raw_value: err.raw_value,
ui_id: err.ui_id,
},
});
}
return {};
};
func.runtime.render.execute_xu_function = async function (options) {
if (options.is_skeleton) return;
const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[options.paramsP.dsSessionP];
const handler_bundle = options.handler_bundle || func.runtime.render.build_xu_handlers({
...options,
$live_elm: options.$live_elm || options.$elm,
}, _ds);
const { common_fx, tag_fx } = handler_bundle;
if (func.runtime.render.is_xu_tag(options.nodeP.tagName)) {
if (options.xu_func === 'xu-exp') {
return await common_fx[options.xu_func](options.$elm, options.val);
}
if (tag_fx?.[options.nodeP.tagName]?.[options.xu_func]) {
return await tag_fx[options.nodeP.tagName][options.xu_func](options.$elm, options.val);
}
if (options.nodeP.tagName === 'xu-panel' && !options.xu_func.startsWith('xu-')) {
if (!globalThis.__XUDA_PANEL_PARAM_GATE_LOGGED__) {
globalThis.__XUDA_PANEL_PARAM_GATE_LOGGED__ = true;
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] panel_parameter_attribute ' + JSON.stringify({
change: 'treat_xu_panel_non_runtime_attribute_as_parameter',
}));
}
return {};
}
console.warn(`attribute ${options.xu_func} not found for ${options.nodeP.tagName}`);
return {};
}
if (xu_isEmpty(func.runtime.ui.get_data(options.$elm))) {
return {};
}
if (options.xu_func !== 'xu-exp') {
func.runtime.render.record_attribute_stat(options.$elm, options.xu_func, options.val.value);
}
try {
if (!common_fx[options.xu_func]) {
await func.runtime.render.report_xu_runtime_error(options, null, `Unknown xu directive: ${options.xu_func}`);
return {};
}
return await common_fx[options.xu_func](options.$elm, options.val);
} catch (error) {
return await func.runtime.render.report_xu_runtime_error(options, error);
}
};
func.events = {};
func.events._debug_summarize_value = function (value) {
if (Array.isArray(value)) {
return {
type: 'array',
length: value.length,
first_keys: value[0] && typeof value[0] === 'object' ? Object.keys(value[0]).slice(0, 8) : [],
};
}
if (value && typeof value === 'object') {
return {
type: 'object',
keys: Object.keys(value).slice(0, 12),
};
}
if (typeof value === 'string') {
return {
type: 'string',
length: value.length,
empty: value.length === 0,
};
}
return {
type: typeof value,
value,
};
};
func.events._debug_summarize_object = function (obj) {
const ret = {};
if (!obj || typeof obj !== 'object') return ret;
for (const [key, value] of Object.entries(obj)) {
ret[key] = func.events._debug_summarize_value(value);
}
return ret;
};
func.events._debug_trace_save_asset = async function (SESSION_ID, label, payload, dsSessionP) {
try {
const fields = {};
for (const field_id of ['files_v', 'view_v', 'wysiwyg_v', 'open_modal_v']) {
try {
const field_ret = await func.datasource.get_value(SESSION_ID, field_id, dsSessionP);
fields[field_id] = {
found: !!field_ret?.found,
dsSessionP: field_ret?.dsSessionP,
currentRecordId: field_ret?.currentRecordId,
value: func.events._debug_summarize_value(field_ret?.ret?.value),
};
} catch (err) {
fields[field_id] = {
error: err?.message || String(err),
};
}
}
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] save_asset_trace ' +
JSON.stringify({
label,
dsSessionP,
...payload,
fields,
}),
);
} catch (err) {
console.warn('[xuda-runtime] save_asset_trace_failed', err);
}
};
func.events.validate = async function (SESSION_ID, triggerP, dsSessionP, eventIdP, sourceP, argumentsP, return_validation_onlyP, event_optionsP) {
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSessionP];
const event_options = event_optionsP && typeof event_optionsP === 'object' ? event_optionsP : {};
var args = {
triggerP,
dsSessionP,
eventIdP,
sourceP,
argumentsP,
return_validation_onlyP,
event_options,
};
const search_event_in_parent_ds = async function () {
if (_ds && typeof _ds.parentDataSourceNo !== 'undefined') {
await func.events.validate(SESSION_ID, triggerP, _ds.parentDataSourceNo, eventIdP, sourceP, argumentsP, return_validation_onlyP, event_options);
}
};
var ret = false;
var jobs = [];
// var success;
if (_ds?.prog_id) {
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
// check view exist
if (!glb.IS_WORKER) _ds.event_stat_obj = {};
if (_view_obj.progEvents) {
if (_session.api_callback && eventIdP) {
_session.api_callback(eventIdP, SESSION_ID, SESSION_OBJ);
}
// check events has rows
for await (let val of _view_obj.progEvents) {
//run events rows
var eventProp = undefined;
if (val.data.type === triggerP) {
// compare event trigger
if ((triggerP !== 'user_defined') | (triggerP === 'user_defined' && eventIdP === val.data.event_name)) {
// compare user defined name
var expCond;
if (val.data.condition) expCond = await func.expression.get(SESSION_ID, val.data.condition, dsSessionP, 'condition');
if (!val.data.condition || expCond.result) {
func.utils.debug.watch(SESSION_ID, _ds.prog_id + '%' + val.id, 'view_event', val, triggerP + ' ' + eventIdP, expCond);
ret = true;
if (return_validation_onlyP) break;
const set_arguments = async function () {
var args = argumentsP || {};
// for await (let [key, fieldId] of Object.entries(
// val.data.parameters
// )) {
// const field_info = func.common.find_item_by_key(
// _view_obj.progFields,
// "field_id",
// fieldId
// );
// var value = await func.common.get_cast_val(
// SESSION_ID,
// "events",
// fieldId,
// field_info.props.fieldType,
// args[fieldId]
// );
// const ret = await func.datasource.get_value(
// SESSION_ID,
// fieldId,
// dsSessionP,
// _ds.currentRecordId
// );
// const datasource_changes = {
// [ret.dsSessionP]: {
// [ret.currentRecordId]: { [fieldId]: value },
// },
// };
// await func.datasource.update(SESSION_ID, datasource_changes);
// }
for await (let [key, fieldId] of Object.entries(val.data.parameters)) {
const field_info = func.common.find_item_by_key(_view_obj.progFields, 'field_id', fieldId);
if (field_info?.data?.type !== 'virtual') {
console.warn('parameter field must be virtual, update ignored');
continue;
}
if (!args[fieldId]) continue;
let value = await func.common.get_cast_val(SESSION_ID, 'events', fieldId, field_info.props.fieldType, args[fieldId].value);
if (!xu_isEmpty(args[fieldId].fx)) {
const fx_ret = await func.expression.get(SESSION_ID, args[fieldId].fx, dsSessionP, 'update');
value = fx_ret.result;
}
// find the target field in the program dataset
const ret = await func.datasource.get_value(SESSION_ID, fieldId, dsSessionP, _ds.currentRecordId);
const datasource_changes = {
[ret.dsSessionP]: {
[ret.currentRecordId]: { [fieldId]: value },
},
};
await func.datasource.update(SESSION_ID, datasource_changes, null, event_options.avoid_refresh === true);
}
await add_event();
};
const add_event = async function () {
const _event = func.common.find_item_by_key_root(_view_obj.progEvents, 'id', val.id);
if (_event.workflow) {
// check if event property exist
if (!_event.workflow || xu_isEmpty(_event.workflow)) return;
// check events has rows
for (const trigger_obj of _event.workflow) {
//run events rows
if (!trigger_obj.data.action) continue;
if (!trigger_obj.data.enabled) continue;
// condition ok
var callingEventId = val.data.event_name;
if (!callingEventId) callingEventId = val.id; // default event id when event name is missing
// eventProp = trigger_obj;
// success = true;
const ref_id = trigger_obj.data.name;
var container = undefined;
var screen_prop = undefined;
if (!glb.IS_WORKER) {
if (_ds.panel_div_id) {
try {
container = '#' + _ds.panel_div_id;
const panel_meta = func.runtime.ui.get_meta_by_element_id(_ds.panel_div_id);
if (panel_meta?.xuData?.panel_info) {
screen_prop = panel_meta.xuData.panel_info.paramsP;
} else {
///////////////
container = '#' + _session.DS_GLB[dsSessionP].screenId;
const screen_meta = func.runtime.ui.get_meta_by_element_id(_session.DS_GLB[dsSessionP].screenId);
if (screen_meta?.xuData) {
screen_prop = screen_meta.xuData.paramsP;
}
if (!screen_meta) {
container = '#' + _session.DS_GLB[dsSessionP].containerId;
}
//////////////
}
} catch (e) {
console.error(e);
}
} else {
container = '#' + _ds.screenId;
const screen_meta = func.runtime.ui.get_meta_by_element_id(_ds.screenId);
if (screen_meta?.xuData) {
screen_prop = screen_meta.xuData.paramsP;
}
if (!screen_meta) {
container = '#' + _ds.containerId;
}
}
} else {
screen_prop = {
callingContainerP: _ds.containerId,
};
}
jobs.push(
await func.events.add_to_queue(
SESSION_ID,
sourceP + ' event',
trigger_obj.id,
null, // was click
trigger_obj.data.action,
ref_id,
container,
null,
_ds.currentRecordId,
null,
trigger_obj.data.name,
null,
null,
dsSessionP,
null,
null,
trigger_obj,
triggerP,
screen_prop,
null, // was target frame id
null,
trigger_obj,
trigger_obj.data.parameter_source_data,
val.id,
null,
args,
null,
null,
event_options,
),
);
}
}
};
if (val.data.parameters) {
await set_arguments();
} else {
await add_event();
}
} else {
if (val.data.condition && !expCond.result) {
func.utils.debug.watch(SESSION_ID, _ds.prog_id + '%' + val.id, 'view_event', val, triggerP + ' ' + eventIdP, expCond, true);
}
}
}
}
}
}
}
if (return_validation_onlyP) return ret;
if (!ret) await search_event_in_parent_ds();
return jobs;
};
func.events.add_to_queue = async function (
SESSION_ID,
typeP,
eventIdP,
triggerP,
functionP,
refIdP,
containerP,
elementP,
rowP,
evt,
descP,
NA_rootScreenIdP,
NA_callingEventIdP,
dsSessionP,
NA_isInitP,
NA_calling_program,
event_propertiesP,
calling_triggerP,
paramsP,
NA_target_frame_idP,
_NA2,
calling_trigger_prop,
argumentsP,
source_event_idP,
calling_job,
args,
$div,
$container,
event_optionsP,
) {
var _session = SESSION_OBJ[SESSION_ID];
var obj = {
SESSION_ID,
typeP,
eventIdP,
triggerP,
functionP,
refIdP,
containerP,
elementP,
rowP,
descP,
dsSessionP,
event_propertiesP,
calling_triggerP,
paramsP,
calling_trigger_prop,
argumentsP,
source_event_idP,
calling_job,
args,
$div,
$container,
event_optionsP,
evt,
job_num: _session.WORKER_OBJ.num,
};
var _ds = _session.DS_GLB[dsSessionP];
if (!_ds) return;
if (typeof dsSessionP !== 'undefined' && dsSessionP !== null) {
obj.prog_id = _ds.prog_id;
obj.parentDataSourceNo = _ds.parentDataSourceNo;
obj.nodeId = _ds.nodeId;
}
///////
if (glb.IS_WORKER && func.utils.is_onscreen_event(functionP)) {
obj.client = true;
if (functionP === 'call_library') {
obj.client = false;
}
if (typeof dsSessionP !== 'undefined' && dsSessionP !== null) {
obj.ds_obj = func.utils.clean_returned_datasource(SESSION_ID, dsSessionP);
}
if (obj.client) {
_session.WORKER_OBJ.num++;
// if (!_session.IS_API)
func.utils.post_back_to_client(SESSION_ID, 'job', _session.worker_id, obj);
return;
}
}
////////
if (calling_job) {
var job_index = func.events.find_job_index(SESSION_ID, calling_job);
try {
if (!_session.WORKER_OBJ.jobs[job_index].splice_count) {
_session.WORKER_OBJ.jobs[job_index].splice_count = 0;
}
_session.WORKER_OBJ.jobs[job_index].splice_count++;
_session.WORKER_OBJ.jobs.splice(job_index + _session.WORKER_OBJ.jobs[job_index].splice_count, 0, obj);
// }
} catch (e) {
console.error('bug');
// _session.WORKER_OBJ.jobs.splice(0, 0, obj);
}
} else {
_session.WORKER_OBJ.jobs.push(obj);
}
_session.WORKER_OBJ.num++;
return _session.WORKER_OBJ.num - 1;
};
func.events.find_job_index = function (SESSION_ID, jobNoP) {
var _session = SESSION_OBJ[SESSION_ID];
var ret = null;
if (!_session.WORKER_OBJ) return ret;
for (const [key, val] of Object.entries(_session.WORKER_OBJ.jobs)) {
if (val && val.job_num == jobNoP) {
ret = key;
break;
}
}
return ret;
};
func.events.execute = async function (
SESSION_ID,
jobNoP,
eventIdP,
triggerP,
functionP,
refIdP,
containerP,
elementP,
rowP,
evt,
descP,
rootScreenIdP,
dsSessionP,
NA_callingEventIdP,
callingSourceP,
NA_isInitP,
event_propertiesP,
calling_triggerP,
calling_jobP,
paramsP,
NA_target_frame_idP,
calling_trigger_prop,
NA_calling_program,
argumentsP,
NA_viewIdP,
NA_nodeIdP,
NA_parentDataSourceNoP,
$div,
event_optionsP,
) {
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSessionP];
const event_options = event_optionsP && typeof event_optionsP === 'object' ? event_optionsP : {};
const avoid_event_refresh = event_options.avoid_refresh === true;
if (functionP === 'update') refIdP = null; // in case of left over when program changing from call function to update
var job_index = func.events.find_job_index(SESSION_ID, jobNoP);
if (_session.WORKER_OBJ.jobs?.[job_index]?.stat === 'busy') {
if (jobNoP) _session.WORKER_OBJ.stat = job_index;
return;
}
if (jobNoP && !_session.WORKER_OBJ.jobs[job_index]) {
_session.WORKER_OBJ.stat = null;
return;
}
if (jobNoP) _session.WORKER_OBJ.stat = job_index;
if (jobNoP && calling_trigger_prop?.props?.async) {
func.events.delete_job(SESSION_ID, jobNoP);
_session.WORKER_OBJ.stat = null;
}
if (_session.WORKER_OBJ.jobs[job_index]) {
_session.WORKER_OBJ.jobs[job_index].stat = 'busy';
}
var dsSession = dsSessionP; // new 2020420
var field_elm = elementP;
var calling_field_id = field_elm;
if (field_elm && typeof field_elm === 'object') calling_field_id = func.runtime.ui.get_attr(field_elm, 'xu-ui-id');
var log_nodeId;
var log_prog_id;
var log_source;
if (_session.DS_GLB[dsSession]?.prog_id) log_prog_id = _session.DS_GLB[dsSession].prog_id;
log_nodeId = log_prog_id + '_' + eventIdP;
var log_prop = callingSourceP;
if (callingSourceP === 'system event') {
log_prop = 'global event';
}
if (calling_field_id) {
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
let _field_obj = func.common.find_item_by_key(_view_obj.progFields, 'field_id', calling_field_id);
log_nodeId = log_prog_id + '_' + eventIdP + '_' + _field_obj?.id;
log_source = calling_field_id;
}
if (elementP) {
log_prop = triggerP;
log_nodeId = log_nodeId + '_ui_prop'; //+"_"+eventIdP;
}
//check condition
var expCond;
if (event_propertiesP) {
// conditional event
if (event_propertiesP?.props?.condition) {
expCond = await func.expression.get(SESSION_ID, event_propertiesP.props.condition, dsSession, 'condition', null, null, null, calling_field_id ? calling_field_id : calling_triggerP, null, descP); // execute expression
if (
/files_v|wysiwyg_v|view_v|open_modal_v/.test(event_propertiesP.props.condition) ||
functionP === 'set_data' ||
String(refIdP?.prog || '') === '1630849293262'
) {
await func.events._debug_trace_save_asset(
SESSION_ID,
'condition_eval',
{
functionP,
ref_prog: refIdP?.prog,
eventIdP,
source_event_id: event_propertiesP?.id || calling_trigger_prop?.id,
condition: event_propertiesP.props.condition,
result: expCond?.result,
error: expCond?.error,
fields: expCond?.fields,
},
dsSession,
);
}
func.utils.debug.log(SESSION_ID, log_nodeId, {
module: 'event',
action: log_prop,
source: log_source,
prop: descP,
details: event_propertiesP.props.condition,
result: expCond.result,
error: expCond.error,
fields: expCond.fields,
type: 'event',
prog_id: log_prog_id,
conditional: true,
});
var cond = expCond.result;
if (!cond || expCond.error) {
// condition failed // === true 03/04/16
func.events.delete_job(SESSION_ID, jobNoP);
await func.events._debug_trace_save_asset(
SESSION_ID,
'condition_skip',
{
functionP,
ref_prog: refIdP?.prog,
eventIdP,
source_event_id: event_propertiesP?.id || calling_trigger_prop?.id,
condition: event_propertiesP.props.condition,
result: expCond?.result,
error: expCond?.error,
fields: expCond?.fields,
},
dsSession,
);
func.utils.debug.watch(SESSION_ID, calling_trigger_prop?.id, functionP, '', '', expCond, true);
return;
}
} // Non conditional event
else {
func.utils.debug.log(SESSION_ID, log_nodeId, {
module: 'event',
action: log_prop,
source: log_source,
prop: descP,
details: null,
result: null,
error: null,
fields: null,
type: 'event',
prog_id: log_prog_id,
});
}
}
// var log_error = function (descP, detailsP, warning) {
// func.utils.debug.log(SESSION_ID, log_nodeId, {
// module: "event",
// action: functionP,
// source: log_source,
// prop: descP,
// details: detailsP,
// result: null,
// error: !warning,
// warning: warning,
// fields: null,
// type: "event",
// prog_id: log_prog_id,
// });
// };
const get_params_obj = async function () {
const _prog_id = await get_prog_id();
const _prog = await func.utils.VIEWS_OBJ.get(SESSION_ID, _prog_id);
if (!_prog) {
func.events.delete_job(SESSION_ID, jobNoP);
return func.utils.debug_report(SESSION_ID, 'func.events.execute', 'Program not found: ' + refIdP.prog, 'E');
}
// get in parameters
var params_obj = {};
if (_prog?.properties?.progParams) {
for await (const [key, val] of Object.entries(_prog.properties.progParams)) {
// if (val.data.dir === 'in') continue;
if (typeof args.parameters_obj_inP?.[val.data.parameter] !== 'undefined') {
if (args.parameters_obj_inP?.[val.data.parameter].fx) {
let ret = await func.expression.get(SESSION_ID, args.parameters_obj_inP?.[val.data.parameter].fx, dsSession, 'parameters');
params_obj[val.data.parameter] = ret.result;
} else {
params_obj[val.data.parameter] = args.parameters_obj_inP?.[val.data.parameter].value;
}
continue;
}
if (
val.data.parameter === 'REDUCE_VALUE' &&
typeof args.parameters_obj_inP?.REDUCE_COUNTER !== 'undefined'
) {
const legacy_reduce_counter = args.parameters_obj_inP.REDUCE_COUNTER;
if (legacy_reduce_counter.fx) {
let ret = await func.expression.get(SESSION_ID, legacy_reduce_counter.fx, dsSession, 'parameters');
params_obj[val.data.parameter] = ret.result;
} else {
params_obj[val.data.parameter] = legacy_reduce_counter.value;
}
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] legacy_reduce_counter_alias ' +
JSON.stringify({
program: _prog.properties.menuName,
parameter: val.data.parameter,
target: params_obj[val.data.parameter],
}),
);
} catch (e) {}
continue;
}
console.warn(`Warning: Program ${_prog.properties.menuName} expected In parameter: ${val.data.parameter} but received null instead`);
}
}
if (functionP === 'set_data' || String(_prog_id || '') === '1630849293262') {
await func.events._debug_trace_save_asset(
SESSION_ID,
'set_data_params',
{
functionP,
target_prog: _prog_id,
menuName: _prog?.properties?.menuName,
params: func.events._debug_summarize_object(params_obj),
},
dsSession,
);
}
return params_obj;
};
const get_prog_id = async function () {
let _prop = args?.calling_trigger_prop?.data?.name?.properties;
let _prog_id = args.prog_id;
if (_prop?.['xu-exp:prog']) {
_prog_id = (await func.expression.get(SESSION_ID, _prop['xu-exp:prog'], dsSession, 'prog_id expression')).result;
}
return _prog_id;
};
// const get_args_property_value = async function (prop_name) {
// let _prop = args?.calling_trigger_prop?.data?.name;
// let _value = _prop[prop_name];
// if (_prop?.[`xu-exp:${prop_name}`]) {
// _value = (
// await func.expression.get(
// SESSION_ID,
// _prop[`xu-exp:${prop_name}`],
// dsSession,
// `${prop_name} expression`
// )
// ).result;
// }
// return _value;
// };
var args = {
prog_id: refIdP?.prog,
screenIdP: refIdP?.prog,
callingFieldIdP: field_elm,
dataSourceNoP: null,
parentDataSourceNoP: dsSession,
triggerIdP: eventIdP,
containerIdP: null,
rowIdP: rowP, //sub_row_idP ? sub_row_idP : rowP,
jobNoP: jobNoP,
callingSourceP: callingSourceP,
calling_jobP: calling_jobP,
screen_dsP: null,
is_panelP: null,
argument_listP: null,
calling_trigger_prop: calling_trigger_prop,
parameters_obj_inP: refIdP?.parameters,
call_screen_propertiesP: refIdP?.properties,
};
const get_runtime_module_with_method = async function (module_name, method_name) {
const load_module = async function () {
const module_ret = await func.common.get_module(SESSION_ID, module_name);
if (typeof module_ret?.[method_name] === 'function') {
return module_ret;
}
if (typeof module_ret?.default?.[method_name] === 'function') {
return module_ret.default;
}
return module_ret;
};
let module_ret = await load_module();
if (typeof module_ret?.[method_name] === 'function') {
return module_ret;
}
for (const key of Object.keys(func.common._import_cache || {})) {
if (key.includes(module_name)) {
delete func.common._import_cache[key];
}
}
if (typeof globalThis !== 'undefined') {
globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ = Date.now();
}
module_ret = await load_module();
if (typeof module_ret?.[method_name] === 'function') {
return module_ret;
}
throw new TypeError(`${module_name}.${method_name} is not available`);
};
const fx = {
Call_window: async function () {
var is_panel;
var $calling_container;
if (_session.WORKER_OBJ.jobs[job_index]) {
if (_session.WORKER_OBJ.jobs[job_index].paramsP) {
$calling_container = func.runtime.ui.find_element_by_id(_session.WORKER_OBJ.jobs[job_index].paramsP.callingContainerP);
} else {
$calling_container = ''; // calling from datasource 0
_session.WORKER_OBJ.jobs[job_index].paramsP = {};
}
}
if (!refIdP.prog) {
func.events.delete_job(SESSION_ID, jobNoP);
return func.utils.debug_report(SESSION_ID, 'func.events.execute', 'Program is empty', 'E');
}
const params_obj = await get_params_obj();
return await func.runtime.ui.init_screen({
SESSION_ID,
prog_id: await get_prog_id(),
sourceScreenP: func.runtime.ui.get_data(containerP)?.xuData?.screenId,
callingDataSource_objP: _session.DS_GLB[dsSession],
$callingContainerP: $calling_container,
triggerIdP: eventIdP,
rowIdP: rowP,
jobNoP,
is_panelP: is_panel,
parameters_obj_inP: params_obj,
source_functionP: functionP,
call_screen_propertiesP: args.call_screen_propertiesP,
});
},
call_modal: async function () {
return await fx.Call_window();
},
call_popover: async function () {
return await fx.Call_window();
},
call_page: async function () {
return await fx.Call_window();
},
call_library: async function () {
let plugin_name = refIdP.plugin_name,
method = refIdP.library_method,
$containerP = $div,
dsP = dsSession,
propsP = refIdP.library_props,
sourceP = descP;
var _session = SESSION_OBJ[SESSION_ID];
const set_SYS_GLOBAL_OBJ_WIDGET_INFO = async function (docP) {
var obj = { ...docP };
obj.date = await func.utils.get_dateTime(SESSION_ID, 'SYS_DATE', docP.date);
obj.time = await func.utils.get_dateTime(SESSION_ID, 'SYS_TIME', docP.date);
var datasource_changes = {
[0]: {
['data_system']: {
['SYS_GLOBAL_OBJ_WIDGET_INFO']: obj,
},
},
};
await func.datasource.update(SESSION_ID, datasource_changes, null, avoid_event_refresh);
};
const call_plugin_api = async function (plugin_nameP, dataP) {
return await func.utils.call_plugin_api(SESSION_ID, plugin_nameP, dataP);
};
const report_error = function (descP, warn) {
func.utils.debug.log(SESSION_ID, _session.DS_GLB[dsP].prog_id + '_' + _session.DS_GLB[dsP].callingMenuId, {
module: 'widgets',
action: 'Init',
source: sourceP,
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'widgets',
prog_id: _session.DS_GLB[dsP].prog_id,
});
};
const get_fields_data = async function (fields, props) {
const report_error = function (descP, warn) {
func.utils.debug.log(SESSION_ID, _session.DS_GLB[dsP].prog_id + '_' + _session.DS_GLB[dsP].callingMenuId, {
module: 'widgets',
action: 'Init',
source: sourceP,
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'widgets',
prog_id: _session.DS_GLB[dsP].prog_id,
});
};
const get_property_value = async function (fieldIdP, val) {
// var value = props[fieldIdP];
var value = props[fieldIdP] || (typeof val.defaultValue === 'function' ? val?.defaultValue?.() : val?.defaultValue);
if (props[`xu-exp:${fieldIdP}`]) {
value = (await func.expression.get(SESSION_ID, props[`xu-exp:${fieldIdP}`], dsP, 'widget property')).result;
}
return func.common.get_cast_val(
SESSION_ID,
'widgets',
fieldIdP,
val.type, //val.type !== "string" || val.type !== "number" ? "string" : val.type,
value,
null,
);
};
var data_obj = {};
var return_code = 1;
// $.each(fields, function (key, val) {
for await (const [key, val] of Object.entries(fields)) {
try {
data_obj[key] = await get_property_value(key, val);
if (!data_obj[key] && val.mandatory) {
return_code = -1;
report_error(`${key} is a mandatory field.`);
break;
}
// console.log(val);
} catch (error) {
console.error('[xuda-runtime] caught xuda_events.js:723:', error);
}
}
return { code: return_code, data: data_obj };
};
try {
const _plugin = APP_OBJ[_session.app_id]?.app_plugins_purchased?.[plugin_name];
const index = await func.utils.get_plugin_resource(SESSION_ID, plugin_name, `${_plugin.manifest['index.mjs'].dist ? 'dist/' : ''}index.mjs`);
const methods = index.methods;
if (methods && !methods[method]) {
return report_error('method not found');
}
const fields_ret = await get_fields_data(methods[method].fields, propsP);
if (fields_ret.code < 0) {
return report_error(fields_ret.data);
}
const fields = fields_ret.data;
const plugin_setup_ret = await func.utils.get_plugin_setup(SESSION_ID, plugin_name);
if (plugin_setup_ret.code < 0) {
return report_error(plugin_setup_ret);
}
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: dsP,
job_id: jobNoP,
});
const params = {
SESSION_ID,
method,
_session,
dsP,
sourceP,
propsP,
plugin_name,
$containerP,
plugin_setup: plugin_setup_ret.data,
report_error,
call_plugin_api,
set_SYS_GLOBAL_OBJ_WIDGET_INFO,
api_utils,
};
const fx = await func.utils.get_plugin_resource(SESSION_ID, plugin_name, `${_plugin.manifest['runtime.mjs'].dist ? 'dist/' : ''}runtime.mjs`);
if (!fx[method]) {
throw `Method: ${method} does not exist`;
}
await fx[method](fields, params);
} catch (err) {
report_error(err);
}
func.events.delete_job(SESSION_ID, jobNoP);
},
call_native_javascript: async function () {
const module = await get_runtime_module_with_method('xuda-event-javascript-module.mjs', 'call_javascript');
const result = await module.call_javascript(SESSION_ID, jobNoP, refIdP, dsSession, false, $div);
await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args, avoid_event_refresh);
return result;
},
call_evaluate_javascript: async function () {
const module = await get_runtime_module_with_method('xuda-event-javascript-module.mjs', 'call_javascript');
const result = await module.call_javascript(SESSION_ID, jobNoP, refIdP, dsSession, true, $div);
await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args, avoid_event_refresh);
return result;
},
execute_native_javascript: async function () {
const module = await get_runtime_module_with_method('xuda-event-javascript-module.mjs', 'run_javascript');
const resolved_element_expr = `(func.runtime.ui && func.runtime.ui.find_xu_ui_in_root && func.runtime.ui.get_first_node ? func.runtime.ui.get_first_node(func.runtime.ui.find_xu_ui_in_root(SESSION_ID, ${JSON.stringify(elementP)})) : null)`;
const result = await module.run_javascript(
SESSION_ID,
jobNoP,
dsSession,
`(async function(el,evt) {
${refIdP.value}
})(${resolved_element_expr},evt)`,
null,
null,
null,
evt,
$div,
);
await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args, avoid_event_refresh);
return result;
},
execute_evaluate_javascript: async function () {
const module = await get_runtime_module_with_method('xuda-event-javascript-module.mjs', 'run_javascript');
const resolved_element_expr = `(func.runtime.ui && func.runtime.ui.find_xu_ui_in_root && func.runtime.ui.get_first_node ? func.runtime.ui.get_first_node(func.runtime.ui.find_xu_ui_in_root(SESSION_ID, ${JSON.stringify(elementP)})) : null)`;
const result = await module.run_javascript(
SESSION_ID,
jobNoP,
dsSession,
`(async function(el,evt) {
${refIdP.value}
})(${resolved_element_expr},evt)`,
true,
null,
null,
evt,
$div,
);
await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args, avoid_event_refresh);
return result;
},
loader_on: async function () {
glb.CURRENT_APP_LOADING = null;
LOADER_ACTIVE = true;
LOADER_TEXT = descP;
// }
func.events.delete_job(SESSION_ID, jobNoP);
// if (callbackP) callbackP();
},
loader_off: async function () {
LOADER_ACTIVE = false;
func.events.delete_job(SESSION_ID, jobNoP);
// if (callbackP) callbackP();
},
emit_event: async function () {
if (refIdP.value) {
// if (descP.value) {
func.runtime.platform.emit(refIdP.value, [_session.DS_GLB[dsSession]]);
} else {
func.utils.debug_report(SESSION_ID, 'func.events.execute', 'Event name missing', 'E');
}
func.events.delete_job(SESSION_ID, jobNoP);
// if (callbackP) callbackP();
},
invoke_action: async function () {
func.utils.debug.watch(SESSION_ID, calling_trigger_prop?.id, functionP, null, null, expCond);
await func.action.execute(SESSION_ID, refIdP.value, _ds, null, null, jobNoP, containerP);
},
raise_event: async function () {
var _ds = _session.DS_GLB[dsSession];
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
if (callingSourceP === 'grid' || callingSourceP === 'form') {
let _field_obj = func.common.find_item_by_key(_view_obj.progFields, 'field_id', field_elm);
var event_name = _field_obj?.workflow?.[eventIdP].name.event;
if (_field_obj?.workflow?.[eventIdP].name?.properties['xu-exp:event']) {
event_name = (await func.expression.get(SESSION_ID, props[`xu-exp:event`], dsSession, 'event_name expression')).result;
}
if (field_elm && event_name) {
// get container for fields events
const dsP = await func.datasource.find_event_dataSource(SESSION_ID, event_name, dsSession);
if (event_name === 'SAVE_ASSET_EVENT') {
await func.events._debug_trace_save_asset(
SESSION_ID,
'raise_event',
{
event_name,
callingSourceP,
dsP,
functionP,
source_prog: _ds?.prog_id,
},
dsSession,
);
}
return await func.datasource.run_events_functions(SESSION_ID, dsP, event_name, jobNoP, null, calling_trigger_prop?.data?.name?.parameters || {}, event_options);
}
// done_execution(event_name);
}
if (callingSourceP.includes('event')) {
let event_name = refIdP.event;
if (refIdP?.properties?.['xu-exp:event']) {
event_name = (await func.expression.get(SESSION_ID, refIdP.properties['xu-exp:event'], dsSession, 'event_name expression')).result;
}
const dsP = await func.datasource.find_event_dataSource(SESSION_ID, event_name, dsSession);
if (event_name === 'SAVE_ASSET_EVENT') {
await func.events._debug_trace_save_asset(
SESSION_ID,
'raise_event',
{
event_name,
callingSourceP,
dsP,
functionP,
source_prog: _ds?.prog_id,
},
dsSession,
);
}
await func.datasource.run_events_functions(SESSION_ID, dsP, event_name, jobNoP, calling_trigger_prop?.props?.async, calling_trigger_prop?.data?.name?.parameters || {}, event_options);
// done_execution(event_name);
}
func.events.delete_job(SESSION_ID, jobNoP);
// if (callbackP) callbackP();
func.utils.debug.watch(SESSION_ID, calling_trigger_prop?.id, functionP, '', '', expCond);
},
get_data: async function () {
const params_obj = await get_params_obj();
if (!(await get_prog_id())) {
func.utils.debug_report(SESSION_ID, 'func.events.execute', `${elementP} > ${triggerP} > ${functionP} > program ${prog} is missing`, 'E');
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
var _ds = _session.DS_GLB[dsSession];
if (!_ds) {
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
if (_ds) {
func.utils.debug.watch(SESSION_ID, calling_trigger_prop?.id, functionP, null, calling_trigger_prop, expCond);
const ret = await func.datasource.create(SESSION_ID, await get_prog_id(), args.dataSourceNoP, args.parentDataSourceNoP, args.containerIdP, args.rowIdP, args.jobNoP, args.calling_trigger_prop, null, null, args.callingSourceP, args.calling_jobP, args.screen_dsP, args.is_panelP, params_obj);
let _ds_new = _session.DS_GLB[ret.dsSessionP];
let parameters = args?.calling_trigger_prop?.data?.name?.parameters;
if (parameters && !xu_isEmpty(parameters)) {
await func.datasource.update_changes_for_out_parameter(SESSION_ID, _ds_new.dsSession, _ds.dsSession, avoid_event_refresh);
}
func.events.delete_job(SESSION_ID, jobNoP);
return _ds_new;
}
},
set_data: async function () {
return this.get_data();
},
batch: async function () {
const result = await this.get_data();
// await set_outputField(SESSION_ID, elementP, triggerP, functionP, dsSessionP, result);
return result;
},
update: async function () {
const resolve_update_field_id = async function (field_expr, iterate_info) {
let trimmed = field_expr?.trim?.() || '';
if (!trimmed) {
return trimmed;
}
const first = trimmed.substring(0, 1);
const last = trimmed.substring(trimmed.length - 1);
if ((first === "'" || first === '"' || first === '`') && last === first) {
trimmed = trimmed.substring(1, trimmed.length - 1).trim();
}
if (/^@?[A-Za-z_][\w\-\:\.]*$/.test(trimmed)) {
return trimmed.substring(0, 1) === '@' ? trimmed.substring(1) : trimmed;
}
let ret_field_id = await func.expression.get(SESSION_ID, trimmed, dsSessionP, 'update', null, null, null, null, null, null, iterate_info);
if (typeof ret_field_id?.result === 'string' && ret_field_id.result.substring(0, 1) === '@') {
return ret_field_id.result.substring(1);
}
return ret_field_id?.result;
};
const obj_values_to_update = func.datasource.get_viewFields_for_update_function(SESSION_ID, calling_trigger_prop, null, dsSessionP);
if (!obj_values_to_update || xu_isEmpty(obj_values_to_update)) {
func.utils.debug_report(SESSION_ID, 'Update values object is empty', '', 'W');
if (jobNoP) func.events.delete_job(SESSION_ID, jobNoP);
return;
}
var updates = [];
for await (const [key, val] of Object.entries(obj_values_to_update)) {
var $element;
var iterate_info = null;
if (elementP) {
const element_meta = func.runtime.ui.get_meta(elementP, 'xuData');
iterate_info = element_meta?.iterate_info || null;
}
let ret_value = await func.expression.get(SESSION_ID, val.val.trim(), dsSessionP, 'update', null, null, null, null, null, null, iterate_info);
let _field_id = await resolve_update_field_id(val.id, iterate_info);
let _value = ret_value.result;
updates.push({ _field_id, _value });
}
let datasource_changes = {};
for await (const change of updates) {
let ret_get_value = await func.datasource.get_value(SESSION_ID, change._field_id, dsSessionP);
if (ret_get_value.found) {
let _ds = _session.DS_GLB[ret_get_value.dsSessionP];
if (!datasource_changes[_ds.dsSession]) {
datasource_changes[_ds.dsSession] = {};
}
if (!datasource_changes[_ds.dsSession][ret_get_value.currentRecordId]) {
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId] = {};
}
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId][change._field_id] = change._value;
}
}
await func.datasource.update(SESSION_ID, datasource_changes, null, avoid_event_refresh, triggerP);
if (_ds.PARAM_OUT_INFO) {
for await (const [key, val] of Object.entries(_ds.PARAM_OUT_INFO)) {
await func.datasource.update_changes_for_out_parameter(SESSION_ID, _ds.dsSession, val.parentDataSourceNo, avoid_event_refresh);
}
}
if (jobNoP) func.events.delete_job(SESSION_ID, jobNoP);
// return changes;
},
call_alert: async function () {
await func.utils.alerts.invoke(SESSION_ID, 'call_alert', refIdP, log_source, dsSession);
func.events.delete_job(SESSION_ID, jobNoP);
},
alert: async function () {
await func.utils.alerts.invoke(SESSION_ID, 'alert', refIdP, log_source, dsSession);
func.events.delete_job(SESSION_ID, jobNoP);
},
delay: async function () {
return new Promise((resolve) => {
setTimeout(function () {
if (jobNoP) func.events.delete_job(SESSION_ID, jobNoP);
resolve();
}, refIdP.value);
});
},
comment: async function () {
if (jobNoP) func.events.delete_job(SESSION_ID, jobNoP);
},
call_project_api: async function () {
const params_obj = await get_params_obj();
const _prog_id = await get_prog_id();
if (!_prog_id) {
func.utils.debug_report(SESSION_ID, 'func.events.execute', `${elementP} > ${triggerP} > ${functionP} > program not defined`, 'E');
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
// if (!output_field) {
// func.utils.debug_report(
// SESSION_ID,
// "func.events.execute",
// `${elementP} >${triggerP} >${functionP} > Output field not defined`,
// "W"
// );
// // func.events.delete_job(SESSION_ID, jobNoP);
// // return;
// }
const api_ret = await func.api.call_project_api(_prog_id, params_obj);
await func.datasource.set_outputField(SESSION_ID, dsSessionP, api_ret, args, avoid_event_refresh);
func.events.delete_job(SESSION_ID, jobNoP);
},
call_system_api: async function () {
const api_method = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'api_method');
if (!api_method) {
func.utils.debug_report(SESSION_ID, 'func.events.execute', `${elementP} >${triggerP} >${functionP} > api_method not defined`, 'E');
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
let payload = {};
const _payload = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'payload');
if (_payload) {
const get_payload_property_value = async function (prop_name) {
let _prop = _payload;
let _value = _prop[prop_name];
if (_prop?.[`xu-exp:${prop_name}`]) {
_value = (await func.expression.get(SESSION_ID, _prop[`xu-exp:${prop_name}`], dsSession, `${prop_name} expression`)).result;
}
return _value;
};
for await (let [key, val] of Object.entries(_payload)) {
// if (key.substring(0, 7) !== "xu-exp:") {
const new_key = key.replaceAll('xu-exp:', ''); //key.substring(0, 7) === "xu-exp:" ? key.substring(7): key
payload[new_key] = await get_payload_property_value(new_key);
// }
}
}
// if (!payload) {
// func.utils.debug_report(
// SESSION_ID,
// "func.events.execute",
// `${elementP} >${triggerP} >${functionP} > payload not defined`,
// "E"
// );
// func.events.delete_job(SESSION_ID, jobNoP);
// return;
// }
const output_field = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'outputField');
// if (!output_field) {
// func.utils.debug_report(
// SESSION_ID,
// "func.events.execute",
// `${elementP} >${triggerP} >${functionP} > Output field not defined`,
// "E"
// );
// func.events.delete_job(SESSION_ID, jobNoP);
// return;
// }
const api_ret = await func.api.call_system_api(api_method, payload);
if (output_field) {
let datasource_changes = {};
let ret_get_value = await func.datasource.get_value(SESSION_ID, output_field, dsSessionP);
if (ret_get_value.found) {
let _ds = _session.DS_GLB[ret_get_value.dsSessionP];
if (!datasource_changes[_ds.dsSession]) {
datasource_changes[_ds.dsSession] = {};
}
if (!datasource_changes[_ds.dsSession][ret_get_value.currentRecordId]) {
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId] = {};
}
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId][output_field] = api_ret;
await func.datasource.update(SESSION_ID, datasource_changes, null, avoid_event_refresh);
}
}
func.events.delete_job(SESSION_ID, jobNoP);
},
call_external_api: async function () {
const method = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'method');
if (!method) {
func.utils.debug_report(SESSION_ID, 'func.events.execute', `${elementP} >${triggerP} >${functionP} > method not defined`, 'E');
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
const url = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'url');
if (!url) {
func.utils.debug_report(SESSION_ID, 'func.events.execute', `${elementP} >${triggerP} >${functionP} > url not defined`, 'E');
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
const payload_arr = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'payload');
const report_conversion_error = function (res, typeP, valP) {
var msg = `${elementP} >${triggerP} >${functionP} > error converting from ${valP} to ${typeP}`;
if (error) {
return func.utils.debug_report(SESSION_ID, msg, '', 'W');
}
func.utils.debug_report(SESSION_ID, msg + ' ' + (source.charAt(0).toUpperCase() + source.slice(1).toLowerCase()) + prog_info, '', 'E');
};
const report_conversion_warn = function (res) {
// number/boolean/bigint -> string is lossless; skip the noise (it routes as an "Unhandled Runtime Error")
if (typeP === 'string' && (typeof valP === 'number' || typeof valP === 'boolean' || typeof valP === 'bigint')) return;
var msg = `${elementP} >${triggerP} >${functionP} > type mismatch auto conversion from value ${valP} to ${typeP}`;
func.utils.debug_report(SESSION_ID, msg + ' ' + (source.charAt(0).toUpperCase() + source.slice(1).toLowerCase()) + prog_info, '', 'W');
};
// var ret = valP;
if (error) {
return report_conversion_error();
}
const module = await func.common.get_module(SESSION_ID, 'xuda-get-cast-util-module.mjs');
// return module.cast(
// typeP,
// valP,
// report_conversion_error,
// report_conversion_warn
// );
var payload = payload_arr.reduce(
(ret, val, key) => {
ret[val.key] = module.cast(val.type, val.val, report_conversion_error, report_conversion_warn);
return ret;
},
{},
);
// if (!payload) {
// func.utils.debug_report(
// SESSION_ID,
// "func.events.execute",
// `${elementP} >${triggerP} >${functionP} > payload not defined`,
// "E"
// );
// func.events.delete_job(SESSION_ID, jobNoP);
// return;
// }
const output_field = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'outputField');
// if (!output_field) {
// func.utils.debug_report(
// SESSION_ID,
// "func.events.execute",
// `${elementP} >${triggerP} >${functionP} > Output field not defined`,
// "E"
// );
// func.events.delete_job(SESSION_ID, jobNoP);
// return;
// }
const api_ret = await func.api.call_external_api(method, url, payload);
if (output_field) {
let datasource_changes = {};
let ret_get_value = await func.datasource.get_value(SESSION_ID, output_field, dsSessionP);
if (ret_get_value.found) {
let _ds = _session.DS_GLB[ret_get_value.dsSessionP];
if (!datasource_changes[_ds.dsSession]) {
datasource_changes[_ds.dsSession] = {};
}
if (!datasource_changes[_ds.dsSession][ret_get_value.currentRecordId]) {
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId] = {};
}
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId][output_field] = api_ret;
await func.datasource.update(SESSION_ID, datasource_changes, null, avoid_event_refresh);
}
}
func.events.delete_job(SESSION_ID, jobNoP);
},
};
// if (functionP.includes("alert")) {
// return await fx.alert();
// } else {
// try {
// console.log("functionP", functionP);
return await fx[functionP]();
// } catch (err) {
// console.error(err);
console.error('[xuda-runtime] caught xuda_events.js:1248:', err);
// }
// }
};
func.events.delete_job = function (SESSION_ID, jobNoP) {
var _session = SESSION_OBJ[SESSION_ID];
var job_index = func.events.find_job_index(SESSION_ID, jobNoP);
// console.log(jobNoP, job_index);
if (!_session.WORKER_OBJ.jobs[job_index]) {
_session.WORKER_OBJ.stat = null;
return;
}
var dsSession = _session.WORKER_OBJ.jobs[job_index].dsSessionP;
let ds_obj = _session?.DS_GLB[dsSession];
if (ds_obj) {
delete SCREEN_BLOCKER_OBJ[ds_obj.screenId + (ds_obj.callingScreenId ? '_' + ds_obj.callingScreenId : '')];
}
if (dsSession && ds_obj?.loops_limit && ds_obj?.loops_count < ds_obj?.loops_limit - 1) {
return;
}
_session.WORKER_OBJ.stat = null;
_session.WORKER_OBJ.jobs.splice(job_index, 1);
};
func.events.delete_job_0 = function (SESSION_ID) {
var job_index = 0;
var _session = SESSION_OBJ[SESSION_ID];
if (!_session.WORKER_OBJ.jobs[job_index]) {
_session.WORKER_OBJ.stat = null;
return;
}
var dsSession = _session.WORKER_OBJ.jobs[job_index].dsSession;
let ds_obj = _session?.DS_GLB[dsSession];
if (ds_obj) {
delete SCREEN_BLOCKER_OBJ[ds_obj.screenId + (ds_obj.callingScreenId ? '_' + ds_obj.callingScreenId : '')];
}
if (dsSession && ds_obj && ds_obj.loops_limit && ds_obj.loops_count < ds_obj.loops_limit - 1) {
return;
}
_session.WORKER_OBJ.stat = null;
_session.WORKER_OBJ.jobs.splice(job_index, 1);
};
func.events.check_jobs_idle = async function (SESSION_ID, jobsP) {
return new Promise((resolve, reject) => {
var _session = SESSION_OBJ[SESSION_ID];
if (!jobsP || (jobsP && jobsP.length === 0)) {
resolve();
return;
}
var listener = setInterval(function () {
var found;
for (const [key, val] of Object.entries(jobsP)) {
for (const [key2, val2] of Object.entries(_session.WORKER_OBJ.jobs)) {
if (key2 === val) {
found = true;
break;
}
}
}
if (!found) {
do_callback();
return;
}
}, 100);
var do_callback = function () {
clearInterval(listener);
resolve();
};
});
};
var loop_detected_obj = {};
setInterval(function () {
loop_detected_obj = {};
}, 1000);
func.events.set_browser_changes = function (dsP, fieldsChangedP) {
if (fieldsChangedP.includes('SYS_GLOBAL_STR_BROWSER_TITLE')) func.runtime.platform.set_title(dsP.dataset_new['SYS_GLOBAL_STR_BROWSER_TITLE']);
};
func.events.execute_PENDING_OPEN_URL_EVENTS = async function () {
for (let [key, url] of Object.entries(PENDING_OPEN_URL_EVENTS)) {
if (url) {
glb.WINDOW_LOCATION_SEARCH = url;
glb.ROOT_ELEMENT_ATTRIBUTES = func.UI.utils.get_root_element_attributes();
const params_obj = func.common.getObjectFromUrl(url, glb.ROOT_ELEMENT_ATTRIBUTES);
if (!params_obj.prog) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_030',
source: 'func.events.execute_PENDING_OPEN_URL_EVENTS',
message: 'prog empty',
type: 'W',
details: {
url,
},
});
return;
}
await func.utils.TREE_OBJ.get(SESSION_ID, params_obj.prog);
let screen_ret = await func.utils.get_screen_obj(SESSION_ID, params_obj.prog);
if (screen_ret) {
await func.runtime.ui.init_screen({
SESSION_ID,
prog_id: params_obj.prog,
sourceScreenP: null,
callingDataSource_objP: null,
$callingContainerP: func.runtime.ui.get_session_root(SESSION_ID),
triggerIdP: null,
rowIdP: null,
jobNoP: null,
is_panelP: null,
parameters_obj_inP: null,
source_functionP: 'pendingUrlEvent_embed',
});
} else {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_010',
source: 'func.events.execute_PENDING_OPEN_URL_EVENTS',
message: 'Program not exist',
type: 'E',
details: {
prog_id: params_obj.prog_id,
prog: params_obj.prog,
},
});
func.UI.utils.progressScreen.show(SESSION_ID, 'Program not exist', null, true);
}
} else {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_030',
source: 'func.events.execute_PENDING_OPEN_URL_EVENTS',
message: 'url empty',
type: 'W',
});
}
}
};
func.events.invoke = async function (event_id, options) {
var _session = SESSION_OBJ[SESSION_ID];
const event_options = options && typeof options === 'object' ? options : { avoid_refresh: options === true };
if (!event_id) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_060',
source: 'func.events.invoke',
message: 'event_id Cannot be empty',
type: 'W',
});
return false;
}
var ds;
for await (const [ds_key, val] of Object.entries(_session.DS_GLB)) {
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, val.prog_id);
if (xu_isEmpty(_view_obj.progEvents)) continue;
if (ds) break;
for await (const [key, val] of Object.entries(_view_obj.progEvents)) {
if (val?.data?.type === 'user_defined' && val.data.event_name === event_id) {
ds = ds_key;
break;
}
}
}
if (!ds) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_060',
source: 'func.events.invoke',
message: 'event_id not found',
type: 'W',
details: {
event_id,
},
});
return false;
}
func.events.validate(SESSION_ID, 'user_defined', ds, event_id, null, null, null, event_options);
};
func.expression = {};
func.expression.get = async function (SESSION_ID, valP, dsSessionP, sourceP, rowIdP, sourceActionP, secondPassP, calling_fieldIdP, fieldsP, debug_infoP, iterate_info, js_script_callback, jobNo, api_output_type) {
class xu_class {
async get() {
if (typeof EXP_BUSY !== 'undefined') {
EXP_BUSY = true;
}
var ret;
var fields = {};
var error;
var warning;
// XU_PERF: object token values are passed to the evaluator through this
// slot array instead of being JSON-embedded into the source text, so the
// source stays identical across rows and compiles once (see secure_eval)
var xu_slot_values = null;
var xu_slot_positions = null;
function evalJson(text) {
return eval('(' + text + ')');
}
if (valP === null) {
ret = '';
} else {
switch (typeof valP) {
case 'string':
ret = valP;
break;
case 'undefined':
ret = '';
break;
case 'boolean':
ret = valP ? 'Y' : 'N';
break;
default:
ret = valP.toString();
break;
}
}
if (ret.includes('&')) ret = ret.replace(/\&/g, '&');
ret = func.utils.replace_studio_drive_url(SESSION_ID, ret);
const end_results = function () {
const replace_quotes = function (ret) {
for (const [key, val] of Object.entries(fields)) {
if (typeof val === 'string') ret = ret.replace('"' + val + '"', val.replace(/"/gi, ''));
}
return ret;
};
if (['update', 'javascript'].includes(sourceP)) {
if (typeof ret === 'string') ret = replace_quotes(ret);
}
const log_error = function () {
if (SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP]) {
func.utils.debug.log(SESSION_ID, SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP].nodeId, {
module: 'expression',
action: sourceP,
source: calling_fieldIdP,
prop: ret,
details: ret,
result: ret,
error: error,
warning: warning,
fields: null,
type: 'exp',
prog_id: SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP].prog_id,
debug_info: debug_infoP,
});
}
};
if (error) log_error();
// if (error || warning) log_error();
if (typeof EXP_BUSY !== 'undefined') {
EXP_BUSY = false;
}
const results = {
result: ret,
fields,
res,
explain: result,
error,
warning,
req: valP,
var_error_found,
};
// console.log('EXP>>>', results);
return results;
// return {
// result: ret,
// fields: fields,
// res: res,
// explain: result,
// error: error,
// warning: warning,
// req: valP,
// var_error_found: var_error_found,
// };
};
const variable_not_exist = async function () {
try {
if (sourceP !== 'arguments') {
if (ret && ret.startsWith('_DATE_')) {
ret = ret.slice(6);
} else if (
ret === 'self' || // bypass eval for 'self'
(ret && ret.length === 10 && ret[4] === '-' && ret[7] === '-') // bypass eval for date 2017-03-22
) {
// date or 'self' — skip eval, return as-is
} else {
ret = await func.expression.secure_eval(SESSION_ID, sourceP, ret, jobNo, dsSessionP, js_script_callback, null, undefined, xu_slot_values);
}
// console.log("AFTER OK", ret)
return end_results();
} else {
// do eval for arithmetic vals
ret = ret.replace(/_NULL/gi, '');
return end_results();
}
} catch (err) {
// console.error(err);
return end_results();
}
};
if (!func.expression.validate_variables(valP)) {
return await variable_not_exist();
}
const validate_email = async function () {
const ret = await func.expression.secure_eval(SESSION_ID, sourceP, valP, jobNo, dsSessionP, js_script_callback, null, true);
return glb.emailRegex.test(ret);
};
if (await validate_email()) {
return await variable_not_exist();
}
// var split = [];
var var_Arr = [];
const get_iterate_value_ret = function (fieldIdP) {
if (!iterate_info || (iterate_info.iterator_key !== fieldIdP && iterate_info.iterator_val !== fieldIdP)) {
return null;
}
const iter_value = iterate_info.iterator_key === fieldIdP ? iterate_info._key : iterate_info._val;
const iter_type = typeof iter_value !== 'undefined' ? {}.toString.call(iter_value).match(/\s([a-zA-Z]+)/)[1].toLowerCase() : 'string';
return {
ret: {
value: iter_value,
type: iter_type,
prop: ['array', 'object'].includes(iter_type) ? iter_value : null,
},
fieldIdP,
currentRecordId: rowIdP,
found: typeof iter_value !== 'undefined',
};
};
const split = func.expression.parse(ret) || [];
// console.log(valP, split);
const split_entries = Object.entries(split);
for (let entry_i = 0; entry_i < split_entries.length; entry_i++) {
// run each field
const [arr_key, val] = split_entries[entry_i];
const key = Number(arr_key);
var_Arr[key] = {};
var_Arr[key].value = val.value;
//--------------
const replace_value_in_string = async function (retP, fieldIdP) {
if (iterate_info?.iterator_key === fieldIdP || iterate_info?.iterator_val === fieldIdP) {
if (iterate_info.iterator_key === fieldIdP) {
retP.value = iterate_info._key;
}
if (iterate_info.iterator_val === fieldIdP) {
retP.value = iterate_info._val;
}
}
const set_value = function (valP) {
if (typeof valP !== 'undefined') {
var_Arr[key].value = valP;
if (typeof valP === 'string') var_Arr[key].type = 'string';
} else {
if (retP.type === 'object') {
var_Arr[key].value = '';
var_Arr[key].type = 'string';
}
}
};
if (sourceP === 'exp' && retP.type !== 'exp') {
var_Arr[key].type = retP.type;
return;
}
if (typeof retP.value !== 'undefined') {
var_Arr[key].type = retP.type;
var_Arr[key].value = typeof retP.value === 'string' && !retP.value.includes('<svg xmlns=') && retP.value.indexOf('\\') === -1 && !['UI Attr EXP', 'update'].includes(sourceP) ? retP.value.replaceAll('"', '\\"') : retP.value; // new Apr 6 2025 fixing "\"how much?\"" // new Jul 29 25 to fix quil extra "\" " source!=="UI Attr EXP"
if ((val.value.indexOf('[') > -1) | (val.value.indexOf('.') > -1)) {
//get values from array '@var==="sss" && @var_B==="sss" && @obj.property===5 && @objA["value"]===123 | @objB["value"].property===1234'
var data = retP.prop;
if (retP.type === 'object') data = retP.value;
var property1, property2;
//check for split situation: @objB[@var].property 1: @objB[ 2:@var].property
if (val.value.indexOf('[') === -1 && val.value.indexOf(']') > -1 && val.value.substr(0, 1) === '@') {
//check situation 2
var prevData = var_Arr[key - 1].value;
var_Arr[key].value = prevData[data]; // @objB[@var]
if (val.value.indexOf('.') > -1) {
const props_split = await func.expression.get_property(val.value);
property2 = props_split.property2;
if (prevData[data]) set_value(prevData[data][property2]);
// var_Arr[key].value = prevData[data][property2]; //@objB[@var].property
}
delete var_Arr[key - 1];
} else {
const props = await func.expression.get_property(val.value);
property1 = props.property1;
property2 = props.property2;
if (property1) {
var_Arr[key].value = data[property1]; // @var["value"] or @var.property
if (property2) {
if (data[property1]) set_value(data[property1][property2]);
}
}
if (property2 && !property1) {
if (
data //data[property2]
) {
set_value(data[property2]);
}
}
}
fields[fieldIdP] = var_Arr[key].value; // added 051017 to allow @SYS_GLOBAL_OBJ_WIDGET_INFO.doc_id
var_Arr[key].fieldId = fieldIdP;
} else {
fields[fieldIdP] = var_Arr[key].value; // update flat no warpers
var_Arr[key].fieldId = fieldIdP;
}
}
};
//>>>>>>>>>>>>>>>>>>
//>>>> start here >>
//>>>>>>>>>>>>>>>>>>
if (val.fieldId) {
// @_THIS
if (val.fieldId && val.fieldId.substr(0, 5) === '_THIS' && calling_fieldIdP && (val.fieldId.length === 5 || (val.fieldId.length > 5 && val.fieldId.substr(5, 1) === '.'))) {
if (val.fieldId.length === 5) val.fieldId = calling_fieldIdP;
else val.fieldId = calling_fieldIdP + val.fieldId(5, val.fieldId.length - 1);
}
if (!sourceP === 'exp') {
var_Arr[key].value = '""';
} // put default
fields[val.fieldId] = var_Arr[key].value;
const ret = get_iterate_value_ret(val.fieldId) || (await func.datasource.get_value(SESSION_ID, val.fieldId, dsSessionP, rowIdP)); // find field in dataSources
await replace_value_in_string(ret.ret, ret.fieldIdP);
}
}
try {
// eval the expression
var res = [];
var exp_exist;
var var_error_found;
// merge arr values
var_Arr.forEach(function (val, key) {
if (sourceP === 'UI Property EXP') {
let ret = func.utils.get_drive_url(SESSION_ID, val.value, true);
if (ret.changed) {
res[key] = ret.value;
return true;
}
}
if (sourceP === 'UI Attr EXP') {
let ret = func.utils.get_drive_url(SESSION_ID, val.value, var_Arr.length == 1 ? false : true);
if (ret.changed) {
res[key] = ret.value;
return true;
}
}
if (val.type === 'exp') {
exp_exist = true;
}
res[key] = val.value;
if (var_Arr.length > 1) {
// complex input
if (!['DbQuery', 'alert', 'exp', 'api_rendered_output'].includes(sourceP) && ['string', 'date'].includes(val.type)) {
res[key] = '`' + val.value + '`';
}
// new Dec 18 2024 for Ishai // json,html,xml,text,css
if (['api_rendered_output'].includes(sourceP) && ['json'].includes(api_output_type) && ['string', 'date'].includes(val.type)) {
res[key] = `"` + val.value + `"`;
}
}
if (val.fieldId && val.value && typeof val.value === 'string') {
if (['query', 'condition', 'range', 'sort', 'locate'].includes(sourceP)) {
if (val.value.indexOf('↵') > -1) {
res[key] = val.value.split('↵').join('');
}
res[key] = res[key].replace(/(\r\n|\n|\r)/gm, ''); //.replace(/"/g,'\"');//.replace(/'/g,"");
}
if (['init', 'update', 'virtual'].includes(sourceP)) {
if (val.value.indexOf('↵') > -1) res[key] = val.value.split('↵').join('\n');
res[key] = res[key].replace(/(\r\n|\n|\r)/gm, '\\n');
}
if (typeof IS_PROCESS_SERVER !== 'undefined') {
res[key] = res[key].replace(/(\r\n|\n|\r)/gm, '<br>');
}
fields[val.fieldId] = res[key];
}
// extract object
if (typeof val.value === 'object' && var_Arr.length > 1) {
const _paren = !Array.isArray(val.value) && !var_Arr[key + 1].value?.includes('.');
let _slotted = false;
if (glb.XU_PERF && sourceP === 'UI Attr EXP') {
// slot-ify: clone preserves the legacy parse-of-stringify isolation
try {
const _snap = val.value === null ? null : func.expression.get_slot_clone(val.value);
xu_slot_values = xu_slot_values || [];
xu_slot_positions = xu_slot_positions || [];
const _slot = xu_slot_values.length;
xu_slot_values.push(_snap);
xu_slot_positions.push({ key: key, slot: _slot, paren: _paren });
res[key] = _paren ? '(__xu_v[' + _slot + '])' : '__xu_v[' + _slot + ']';
_slotted = true;
} catch (clone_err) {
_slotted = false;
}
}
if (!_slotted) {
if (_paren) {
// prevent cast on single value expression
res[key] = '(' + JSON.stringify(val.value) + ')';
} else {
res[key] = JSON.stringify(val.value);
}
}
}
if (!exp_exist && sourceP !== 'exp' && val.value && typeof val.value === 'string' && val.value.substr(0, 1) === '@') {
warning = 'Error encoding ' + val.value;
var_error_found = true;
res[key] = 0;
}
});
const join = function (arrP) {
return arrP.join('');
};
var exp = undefined;
if (exp_exist && sourceP !== 'exp') {
// the recursive pass has no access to this call's slot array, so
// materialize slots back into legacy JSON text before recursing
if (xu_slot_values && xu_slot_positions) {
for (let sp = 0; sp < xu_slot_positions.length; sp++) {
const p = xu_slot_positions[sp];
const txt = JSON.stringify(xu_slot_values[p.slot]);
res[p.key] = p.paren ? '(' + txt + ')' : txt;
}
xu_slot_values = null;
xu_slot_positions = null;
}
exp = await func.expression.get(SESSION_ID, join(res), dsSessionP, sourceP, rowIdP, sourceActionP, true, calling_fieldIdP, fields, debug_infoP);
if (exp.res) res = exp.res;
// do second pass when exp exist
else res = [exp.result];
fields = Object.assign(exp.fields, fieldsP);
}
var result = join(res);
if (res.length === 1) {
// bypass join for single value expression
result = res[0];
}
if (secondPassP) {
ret = result;
} else if (sourceP !== 'exp') {
// no eval for second pass
// return single value
if (res.length === 1 && typeof res[0] === 'string' && typeof res[0] !== 'object') {
// avoid eval when query leading zeros problem
ret = join(res);
if (ret && ret.substr(0, 1) === '@') {
error = 'Error encoding @ var';
var_error_found = true;
}
} else {
if (
![
'arguments',
// "alert",
'api_rendered_output',
'DbQuery',
].includes(sourceP)
) {
ret = await func.expression.secure_eval(SESSION_ID, sourceP, result, jobNo, dsSessionP, js_script_callback, null, undefined, xu_slot_values);
} else {
if (sourceP === 'DbQuery') {
ret = JSON.stringify(evalJson(result));
} else {
// try eval
ret = result;
}
}
}
}
// console.log("AAAA")
return end_results();
} catch (err) {
ret = result; // skip eval on error
error = err.message;
return end_results();
}
}
}
const new_class = new xu_class();
return new_class.get();
};
// func.expression.get_bad1 = async function (SESSION_ID, valP, dsSessionP, sourceP, rowIdP, sourceActionP, secondPassP, calling_fieldIdP, fieldsP = {}, debug_infoP, iterate_info, js_script_callback, jobNo, api_output_type) {
// const evalJson = (text) => eval(`(${text})`);
// const replaceQuotes = (str) => {
// for (const [key, val] of Object.entries(fields)) {
// if (typeof val === 'string') str = str.replace(`"${val}"`, val.replace(/"/g, ''));
// }
// return str;
// };
// let ret, error, warning, var_error_found;
// const fields = { ...fieldsP };
// // Initial value processing
// if (valP === null || typeof valP === 'undefined') ret = '';
// else if (typeof valP === 'boolean') ret = valP ? 'Y' : 'N';
// else ret = valP.toString();
// ret = ret.replace(/\&/g, '&');
// ret = func.utils.replace_studio_drive_url(SESSION_ID, ret);
// // End results helper
// const endResults = () => {
// if (['update', 'javascript'].includes(sourceP) && typeof ret === 'string') {
// ret = replaceQuotes(ret);
// }
// if ((error || warning) && SESSION_OBJ[SESSION_ID]?.DS_GLB[dsSessionP]) {
// func.utils.debug.log(SESSION_ID, SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP].nodeId, {
// module: 'expression',
// action: sourceP,
// source: calling_fieldIdP,
// prop: ret,
// details: ret,
// result: ret,
// error,
// warning,
// fields: null,
// type: 'exp',
// prog_id: SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP].prog_id,
// debug_info: debug_infoP,
// });
// }
// return { result: ret, fields, error, warning, req: valP, var_error_found };
// };
// // Handle non-variable cases
// const handleNonVariable = async () => {
// try {
// if (sourceP !== 'arguments') {
// if (ret.startsWith('_DATE_')) ret = ret.slice(6);
// else if (/^\d{4}-\d{2}-\d{2}$/.test(ret) || ret === 'self') return endResults();
// else ret = await func.expression.secure_eval(SESSION_ID, sourceP, ret, jobNo, dsSessionP, js_script_callback);
// } else {
// ret = ret.replace(/_NULL/gi, '');
// }
// return endResults();
// } catch (err) {
// error = err.message;
// return endResults();
// }
// };
// // Early return for simple cases
// if (!func.expression.validate_variables(valP)) return await handleNonVariable();
// if (glb.emailRegex.test(await func.expression.secure_eval(SESSION_ID, sourceP, valP, jobNo, dsSessionP, js_script_callback))) {
// return await handleNonVariable();
// }
// // Parse and process variables
// const split = func.expression.parse(ret) || [];
// const var_Arr = await Promise.all(
// split.map(async (val, key) => {
// const result = { value: val.value, fieldId: val.fieldId };
// if (!val.fieldId) return result;
// // Handle _THIS substitution
// if (val.fieldId.startsWith('_THIS') && calling_fieldIdP) {
// result.fieldId = val.fieldId.length === 5 ? calling_fieldIdP : calling_fieldIdP + val.fieldId.slice(5);
// }
// // Fetch value from datasource
// const { ret: fetchedValue, fieldIdP } = await func.datasource.get_value(SESSION_ID, result.fieldId, dsSessionP, rowIdP);
// result.value = fetchedValue?.value ?? (sourceP === 'exp' ? fetchedValue?.value : '""');
// result.type = fetchedValue?.type;
// // Handle iteration
// if (iterate_info) {
// if (iterate_info.iterator_key === fieldIdP) result.value = iterate_info._key;
// if (iterate_info.iterator_val === fieldIdP) result.value = iterate_info._val;
// }
// // Process nested properties
// if (val.value.includes('[') || val.value.includes('.')) {
// const { property1, property2 } = await func.expression.get_property(val.value);
// const data = fetchedValue?.type === 'object' ? fetchedValue.value : fetchedValue?.prop;
// if (key > 0 && val.value.includes(']') && !val.value.includes('[') && split[key - 1].value) {
// const prevData = split[key - 1].value;
// result.value = prevData[fieldIdP];
// if (val.value.includes('.') && prevData[fieldIdP]) {
// result.value = prevData[fieldIdP][property2] ?? '';
// }
// } else if (data) {
// if (property1) result.value = data[property1] ?? '';
// if (property2) result.value = (property1 ? data[property1]?.[property2] : data[property2]) ?? '';
// }
// }
// fields[fieldIdP] = result.value;
// return result;
// }),
// );
// // Final evaluation
// try {
// const res = var_Arr.map((val, key) => {
// if (sourceP === 'UI Property EXP' || sourceP === 'UI Attr EXP') {
// const { changed, value } = func.utils.get_drive_url(SESSION_ID, val.value, sourceP === 'UI Attr EXP' && var_Arr.length > 1);
// if (changed) return value;
// }
// let value = val.value;
// if (var_Arr.length > 1) {
// if (!['DbQuery', 'alert', 'exp', 'api_rendered_output'].includes(sourceP) && ['string', 'date'].includes(val.type)) {
// value = `\`${value}\``;
// } else if (sourceP === 'api_rendered_output' && api_output_type === 'json' && ['string', 'date'].includes(val.type)) {
// value = `"${value}"`;
// }
// }
// if (val.fieldId && typeof value === 'string') {
// if (['query', 'condition', 'range', 'sort', 'locate'].includes(sourceP)) value = value.replace(/↵|\r\n|\n|\r/g, '');
// if (['init', 'update', 'virtual'].includes(sourceP)) value = value.replace(/↵|\r\n|\n|\r/g, '\\n');
// if (typeof IS_PROCESS_SERVER !== 'undefined') value = value.replace(/↵|\r\n|\n|\r/g, '<br>');
// fields[val.fieldId] = value;
// }
// if (typeof value === 'object' && var_Arr.length > 1) {
// value = Array.isArray(value) || var_Arr[key + 1]?.value?.includes('.') ? JSON.stringify(value) : `(${JSON.stringify(value)})`;
// }
// if (!val.type === 'exp' && sourceP !== 'exp' && typeof value === 'string' && value.startsWith('@')) {
// warning = `Error encoding ${value}`;
// var_error_found = true;
// return '0';
// }
// return value;
// });
// ret = res.length === 1 ? res[0] : res.join('');
// if (var_Arr.some((v) => v.type === 'exp') && sourceP !== 'exp' && !secondPassP) {
// const exp = await func.expression.get(SESSION_ID, ret, dsSessionP, sourceP, rowIdP, sourceActionP, true, calling_fieldIdP, fields, debug_infoP);
// ret = exp.res?.[0] ?? exp.result;
// Object.assign(fields, exp.fields);
// } else if (!secondPassP && !['arguments', 'api_rendered_output', 'DbQuery'].includes(sourceP)) {
// ret = await func.expression.secure_eval(SESSION_ID, sourceP, ret, jobNo, dsSessionP, js_script_callback);
// } else if (sourceP === 'DbQuery') {
// ret = JSON.stringify(evalJson(ret));
// }
// if (typeof ret === 'string' && ret.startsWith('@')) {
// error = 'Error encoding @ var';
// var_error_found = true;
// }
// } catch (err) {
// error = err.message;
// }
// return endResults();
// };
// func.expression.parse_org = function (strP) {
// var extract_str = function (strP, posP) {
// if (!posP) posP = 0;
// var clean_split_str = function (arrP) {
// var arr = [];
// if (arrP && arrP.length > 1 && arrP[0] === '' && arrP[1].indexOf('@') > -1) {
// for (var i = 1; i <= arrP.length; i++) {
// arr.push(arrP[i]);
// }
// return arr;
// } else return arrP;
// };
// var nonLettersPatt = /\W/; // non letters
// var validSymbolsNoArray = /[^.@\[]/; //valid symbols no array /[^.@\[\]\]]/
// var validSymbolsWithArray = /[^.@"'\[\]]/; //valid symbols with array
// var validSymbols = validSymbolsNoArray;
// var splitTmp = strP.replace(/@/g, '^^@').split('^^');
// var split = clean_split_str(splitTmp);
// var obj = [];
// if (split) {
// for (let val of split) {
// // run on @ segments
// if (val) {
// var pos = strP.indexOf(val);
// if (val && val.substr(0, 1) === '@') {
// var tmpStr = '';
// var word_start_pos = undefined;
// var word_end_pos = undefined;
// // run on @ segment string
// for (var i = 0; i <= val.length; i++) {
// var key1 = i;
// var val1 = val.substr(i, 1);
// if (
// val1 === '.' &&
// !word_start_pos // find first dot
// )
// word_start_pos = key1;
// if (
// word_start_pos &&
// key1 > word_start_pos &&
// nonLettersPatt.test(val1) // find any sign character to mark the end of word
// )
// word_end_pos = key1;
// if (word_start_pos && word_start_pos >= 0 && word_end_pos && word_end_pos >= 0) {
// // find the word
// var word = val.substr(word_start_pos + 1, word_end_pos - word_start_pos - 1); // get the word
// // if (glb.ALL_PROPERTIES_ARR.indexOf(word) === -1) {
// // compare with internal properties
// tmpStr = tmpStr.substr(0, word_start_pos) + '^^' + tmpStr.substr(word_start_pos, word_end_pos);
// // }
// if (val.substr(word_end_pos, 1) === '.') word_start_pos = word_end_pos;
// else word_start_pos = null;
// word_end_pos = null;
// }
// if (val1 === '[') validSymbols = validSymbolsWithArray;
// if (nonLettersPatt.test(val1) && validSymbols.test(val1) && tmpStr.indexOf('^^') === -1) {
// tmpStr += '^^' + val1;
// } else tmpStr += val1;
// }
// // });
// if (tmpStr.indexOf('^^') > -1) {
// var obj1 = extract_str(tmpStr, pos);
// obj = obj.concat(obj1);
// } else {
// // push clean @var
// var fieldId = undefined;
// if (val) {
// fieldId = val.substr(1, val.length);
// if (val.indexOf('.') > -1) fieldId = val.substr(1, val.indexOf('.') - 1);
// if (val.indexOf('[') > -1) fieldId = val.substr(1, val.indexOf('[') - 1);
// //
// // if (val.indexOf("]") > -1)
// // fieldId = val.substr(1, val.indexOf("]") - 1);
// }
// obj.push({
// value: val,
// fieldId: fieldId,
// pos: pos + posP,
// });
// }
// } else {
// obj.push({
// value: val,
// pos: pos + posP,
// });
// }
// }
// }
// return obj;
// }
// };
// var res = extract_str(strP);
// return res;
// };
func.expression._parse_cache = new Map();
func.expression.parse = function (input) {
if (typeof input !== 'string') return [];
if (func.expression._parse_cache.has(input)) {
return func.expression._parse_cache.get(input).map(function (s) { return Object.assign({}, s); });
}
const segments = [];
let pos = 0;
const parts = input.split(/(@\w+)/).filter(Boolean);
for (const part of parts) {
if (part.startsWith('@')) {
const fieldId = part.slice(1);
segments.push({
value: part,
fieldId,
pos,
});
} else {
segments.push({
value: part,
pos,
});
}
pos += part.length;
}
// Evict oldest entry if cache exceeds limit
if (func.expression._parse_cache.size >= 500) {
const firstKey = func.expression._parse_cache.keys().next().value;
func.expression._parse_cache.delete(firstKey);
}
func.expression._parse_cache.set(input, segments);
return segments.map(function (s) { return Object.assign({}, s); });
};
func.expression.get_property = async function (valP) {
async function secure_eval(val) {
if (typeof IS_PROCESS_SERVER === 'undefined') {
try {
return eval(val);
} catch (err) {
console.error(err);
return;
}
}
try {
let vm = new VM({
sandbox: {
func: func,
SESSION_ID: SESSION_ID,
SESSION_OBJ: { [`${SESSION_ID}`]: SESSION_OBJ[SESSION_ID] },
},
timeout: 1000,
allowAsync: false,
});
return await vm.run(val);
} catch (err) {
throw err;
}
}
var property1, property2;
if (valP.indexOf('[') > -1 && valP.indexOf(']') > -1) {
property1 = valP.substr(valP.indexOf('[') + 1, valP.indexOf(']') - valP.indexOf('[') - 1); // get []
property1 = await secure_eval(property1);
}
if (valP.indexOf('.') > -1) property2 = valP.substr(valP.indexOf('.') + 1, valP.length); // get .
return {
property1: property1,
property2: property2,
};
};
// func.expression.get_property_bad = async function (valP) {
// if (typeof valP !== 'string') return { property1: undefined, property2: undefined };
// const secureEval = async (expr) => {
// if (typeof IS_PROCESS_SERVER === 'undefined') {
// try {
// return eval(expr);
// } catch (err) {
// console.error(err);
// return undefined;
// }
// }
// try {
// const vm = new VM.VM({
// sandbox: {
// func,
// SESSION_ID,
// SESSION_OBJ: { [SESSION_ID]: SESSION_OBJ[SESSION_ID] },
// },
// timeout: 1000,
// allowAsync: false,
// });
// return await vm.run(expr);
// } catch {
// return undefined; // Simplified error handling
// }
// };
// let property1, property2;
// const bracketStart = valP.indexOf('[');
// const bracketEnd = valP.indexOf(']');
// if (bracketStart > -1 && bracketEnd > bracketStart) {
// const expr = valP.slice(bracketStart + 1, bracketEnd);
// property1 = await secureEval(expr);
// }
// const dotIndex = valP.indexOf('.');
// if (dotIndex > -1) {
// property2 = valP.slice(dotIndex + 1);
// }
// return { property1, property2 };
// };
func.expression.validate_constant = function (valP) {
var patt = /["']/;
if (typeof valP === 'string' && patt.test(valP.substr(0, 1)) && patt.test(valP.substr(0, valP.length - 1))) return true;
else return false;
};
func.expression.validate_variables = function (valP) {
if (typeof valP === 'string' && valP.indexOf('@') > -1) return true;
else return false;
};
func.expression.remove_quotes = function (valP) {
if (func.expression.validate_constant(valP)) return valP.substr(1, valP.length - 2);
else return valP;
};
// func.expression.validate_constant = function (valP) {
// typeof valP === 'string' && /^["'].*["']$/.test(valP);
// };
// func.expression.validate_variables = function (valP) {
// typeof valP === 'string' && valP.includes('@');
// };
// func.expression.remove_quotes = function (valP) {
// func.expression.validate_constant(valP) && typeof valP === 'string' ? valP.slice(1, -1) : valP;
// };
// func.expression.secure_eval_org = async function (SESSION_ID, sourceP, val, job_id, dsSessionP, js_script_callback, evt) {
// const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
// func,
// glb,
// SESSION_OBJ,
// SESSION_ID,
// APP_OBJ,
// dsSession: dsSessionP,
// job_id,
// });
// const xu = api_utils;
// if (typeof IS_PROCESS_SERVER === 'undefined' && typeof IS_DOCKER === 'undefined') {
// try {
// return eval(val);
// } catch (err) {
// try {
// return JSON5.parse(val);
// } catch (err) {
// // console.error(err);
// return val;
// }
// }
// }
// // server side execution
// if (sourceP === 'javascript') {
// process.on('uncaughtException', (err) => {
// console.error('Asynchronous error caught.', err);
// func.events.delete_job(SESSION_ID, job_id);
// if (typeof IS_PROCESS_SERVER !== 'undefined' || typeof IS_DOCKER !== 'undefined') {
// if (SESSION_OBJ[SESSION_ID].crawler) return;
// return __.rpi.write_log(SESSION_OBJ[SESSION_ID].app_id, 'error', 'worker', 'vm error', err, null, val, 'func.expression.get.secure_eval');
// }
// });
// try {
// const dir = path.join(_conf.studio_drive_path, SESSION_OBJ[SESSION_ID].app_id, 'node_modules', '/');
// const script = new VM.VMScript(`try{${val}}catch(e){func.api.error(SESSION_ID, "nodejs error", e); console.error(e); func.events.delete_job(SESSION_ID, job_id);}`, { filename: dir, dirname: dir });
// let vm = new VM.NodeVM({
// require: {
// external: true,
// },
// sandbox: {
// func,
// xu,
// SESSION_ID,
// SESSION_OBJ: { [`${SESSION_ID}`]: SESSION_OBJ[SESSION_ID] },
// callback: js_script_callback,
// job_id,
// axios,
// got,
// FormData,
// },
// timeout: 60000,
// });
// return await vm.run(script, {
// filename: dir,
// dirname: dir,
// });
// } catch (err) {
// console.error('Failed to execute script.', err);
// if (typeof IS_PROCESS_SERVER !== 'undefined') {
// func.events.delete_job(SESSION_ID, jobNo);
// return __.db.add_error_log(SESSION_OBJ[SESSION_ID].app_id, 'api', err);
// }
// }
// } else {
// try {
// try {
// let vm = new VM.VM({
// sandbox: {
// xu,
// func,
// SESSION_ID,
// SESSION_OBJ: { [`${SESSION_ID}`]: SESSION_OBJ[SESSION_ID] },
// callback: js_script_callback,
// job_id,
// },
// timeout: 1000,
// allowAsync: false,
// });
// let ret = val;
// if (typeof val === 'string') {
// ret = await vm.run(val);
// }
// return ret;
// } catch (err) {
// throw '';
// }
// } catch (err) {
// try {
// return JSON5.parse(val);
// } catch (err) {
// // console.error(err);
// return val;
// }
// }
// }
// };
// One clone per object per update epoch: many attribute expressions binding
// the same large object (e.g. a whole tree field) would otherwise re-clone it
// per evaluation. datasource.update clears the cache, so expressions never see
// values from before the mutation that triggered their refresh.
func.expression.get_slot_clone = function (v) {
const cache = (func.expression._slot_clone_cache = func.expression._slot_clone_cache || new WeakMap());
let c = cache.get(v);
if (typeof c === 'undefined') {
c = structuredClone(v);
cache.set(v, c);
}
return c;
};
func.expression.secure_eval = async function (SESSION_ID, sourceP, val, job_id, dsSessionP, js_script_callback, evt, ignore_errors, xu_values) {
if (typeof val !== 'string') return val;
const xu = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: dsSessionP,
job_id,
});
const isServer = typeof IS_PROCESS_SERVER !== 'undefined' || typeof IS_DOCKER !== 'undefined';
// visible to both the compiled path and the direct eval below
const __xu_v = xu_values;
// Client-side execution
if (!isServer) {
// XU_PERF: compile once per distinct source instead of parsing on every
// eval. Non-expression sources (multi-statement javascript) and sources
// that fail to compile stay on the legacy eval; any runtime error falls
// through to the legacy path so error semantics are unchanged.
if (glb.XU_PERF && sourceP !== 'javascript') {
// compile on the SECOND sighting: repeated sources (row templates) get a
// compiled function, one-off sources (unique per-node paths) stay on
// plain eval and never pay Function-compile cost
const cache = (func.expression._compiled_exp_cache = func.expression._compiled_exp_cache || new Map());
const entry = cache.get(val);
if (typeof entry === 'function') {
try {
return entry(xu, func, glb, SESSION_ID, SESSION_OBJ, dsSessionP, job_id, js_script_callback, evt, __xu_v);
} catch (compiled_err) {
// fall through to the legacy eval below
}
} else if (entry === 1) {
let fn = null;
try {
fn = new Function('xu', 'func', 'glb', 'SESSION_ID', 'SESSION_OBJ', 'dsSessionP', 'job_id', 'js_script_callback', 'evt', '__xu_v', 'return (' + val + '\n);');
} catch (compile_err) {
fn = null; // uncompilable: stays null, legacy eval from here on
}
cache.set(val, fn);
if (fn) {
try {
return fn(xu, func, glb, SESSION_ID, SESSION_OBJ, dsSessionP, job_id, js_script_callback, evt, __xu_v);
} catch (compiled_err) {
// fall through to the legacy eval below
}
}
} else if (typeof entry === 'undefined') {
if (cache.size > 4000) cache.clear();
cache.set(val, 1);
}
}
try {
return eval(val);
} catch (err) {
if (sourceP === 'javascript' && !ignore_errors) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EXP_010',
source: 'func.expression.secure_eval',
message: 'Execution error',
type: 'E',
err,
details: {
sourceP,
dsSessionP,
job_id,
},
});
}
try {
return JSON5.parse(val);
} catch (json_error) {
return val;
}
}
}
// Server-side execution
const sandbox = {
func,
xu,
SESSION_ID,
SESSION_OBJ: { [SESSION_ID]: SESSION_OBJ[SESSION_ID] },
callback: js_script_callback,
job_id,
...(sourceP === 'javascript' ? { axios, got, FormData } : {}),
};
const handleError = async (err) => {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EXP_010',
source: 'func.expression.secure_eval',
message: 'Execution error',
type: 'E',
err,
details: {
sourceP,
dsSessionP,
job_id,
},
});
func.events.delete_job(SESSION_ID, job_id);
if (isServer && !SESSION_OBJ[SESSION_ID].crawler) {
if (sourceP !== 'javascript') {
__.db.add_error_log(SESSION_OBJ[SESSION_ID].app_id, 'api', err);
}
}
return val; // Fallback to original value
};
if (sourceP === 'javascript') {
process.on('uncaughtException', function (uncaught_error) {
handleError(uncaught_error);
});
try {
const dir = path.join(_conf.studio_drive_path, SESSION_OBJ[SESSION_ID].app_id, 'node_modules');
const script = new VMScript(`try { ${val} } catch (e) { func.api.error(SESSION_ID, "nodejs error", e); throw e; }`, { filename: dir, dirname: dir });
const vm = new NodeVM({
require: { external: true },
sandbox,
timeout: 60000,
});
return await vm.run(script, { filename: dir, dirname: dir });
} catch (err) {
return await handleError(err);
}
}
try {
const vm = new VM({
sandbox,
timeout: 1000,
allowAsync: false,
});
return await vm.run(val);
} catch {
try {
return JSON5.parse(val);
} catch {
return val;
}
}
};
func.UI.screen = {};
func.UI.screen.init = async function (SESSION_ID, prog_id, sourceScreenP, callingDataSource_objP, $callingContainerP, triggerIdP, rowIdP, jobNoP, is_panelP, parameters_obj_inP, source_functionP, call_screen_propertiesP, refreshed_ds, parameters_raw_obj) {
return await func.runtime.ui.init_screen({
SESSION_ID,
prog_id,
sourceScreenP,
callingDataSource_objP,
$callingContainerP,
triggerIdP,
rowIdP,
jobNoP,
is_panelP,
parameters_obj_inP,
source_functionP,
call_screen_propertiesP,
refreshed_ds,
parameters_raw_obj,
});
};
func.UI.screen.update_SYS_OBJ_WIN_INFO = function (SESSION_ID, dsNoP) {
return func.runtime.ui.update_sys_obj_win_info(SESSION_ID, dsNoP);
};
func.UI.screen.validate_exit_events = async function (SESSION_ID, div_data_paramsP, forceP) {
return await func.runtime.ui.validate_exit_events(SESSION_ID, div_data_paramsP, forceP);
};
func.UI.screen.call_embed = function (SESSION_ID, prog) {
return func.runtime.ui.call_embed(SESSION_ID, prog);
};
func.UI.screen.refresh_xu_attributes = async function (SESSION_ID, fields_arr, jobNoP, $elm_to_search, dsSession_changed, avoid_xu_for_refresh, trigger) {
return await func.runtime.ui.refresh_xu_attributes({
SESSION_ID,
fields_arr,
jobNoP,
$elm_to_search,
dsSession_changed,
avoid_xu_for_refresh,
trigger,
});
};
func.UI.screen.refresh_screen = async function (SESSION_ID, fields_changed_arr, datasource_changed, fields_changed_datasource, watcher) {
return await func.runtime.ui.refresh_screen({
SESSION_ID,
fields_changed_arr,
datasource_changed,
fields_changed_datasource,
watcher,
});
};
func.UI.screen.execute_xu_functions = async function (SESSION_ID, is_skeleton, $root_container, nodeP, $container, paramsP, parent_infoP, jobNoP, keyP, parent_nodeP, xu_func, $elm, val, is_init, refreshed_ds) {
if (is_skeleton) return;
return await func.runtime.render.execute_xu_function({
SESSION_ID,
is_skeleton,
$root_container,
nodeP,
$container,
paramsP,
parent_infoP,
jobNoP,
keyP,
parent_nodeP,
xu_func,
$elm,
val,
is_init,
refreshed_ds,
get_params_obj_new: func.runtime.program.get_params_obj,
});
};
func.UI.screen.fix_val_defaults = function (key, val) {
return func.runtime.render.fix_val_defaults(key, val);
};
func.UI.screen.set_attributes_new = async function (SESSION_ID, is_skeleton, $root_container, nodeP, $container, paramsP, parent_infoP, jobNoP, keyP, parent_nodeP, $elm, is_init, execute_attributes = [], refreshed_ds) {
return await func.runtime.render.set_attributes_new({
SESSION_ID,
is_skeleton,
$root_container,
nodeP,
$container,
paramsP,
parent_infoP,
jobNoP,
keyP,
parent_nodeP,
$elm,
is_init,
execute_attributes,
refreshed_ds,
});
};
func.UI.screen.panel_post_render_handler = async function (SESSION_ID, $container, $wrapper, nodeP, $panel_div, jobNoP) {
return await func.runtime.ui.panel_post_render_handler({
SESSION_ID,
$container,
$wrapper,
nodeP,
$panel_div,
jobNoP,
});
};
func.UI.screen.create_container = async function (SESSION_ID, $root_container, nodeP, $container, paramsP, parent_infoP, jobNoP, keyP, parent_nodeP, prop, classP, elem_propP, div_typeP, $appendToP, attr_str, is_placeholder) {
return await func.runtime.ui.create_container({
SESSION_ID,
$root_container,
nodeP,
$container,
paramsP,
parent_infoP,
jobNoP,
keyP,
parent_nodeP,
prop,
classP,
elem_propP,
div_typeP,
$appendToP,
attr_str,
is_placeholder,
});
};
func.UI.screen.execute_screen_ready_events = async function (SESSION_ID, paramsP, sourceP, $div, jobNoP, $div_objP) {
return await func.runtime.ui.execute_screen_ready_events({
SESSION_ID,
paramsP,
sourceP,
$div,
jobNoP,
$div_objP,
});
};
func.UI.screen.screen_loading_done = async function (SESSION_ID, paramsP, $div, jobNoP) {
return await func.runtime.ui.screen_loading_done({
SESSION_ID,
paramsP,
$div,
jobNoP,
});
};
func.UI.screen.render_ui_tree = async function (SESSION_ID, $container, nodeP, parent_infoP, paramsP, jobNoP, is_skeleton, keyP, refreshed_ds, parent_nodeP, check_existP, $root_container) {
return await func.runtime.render.render_ui_tree(SESSION_ID, $container, nodeP, parent_infoP, paramsP, jobNoP, is_skeleton, keyP, refreshed_ds, parent_nodeP, check_existP, $root_container);
};
func.UI.screen.refresh_document_changes_for_realtime_update = async function (SESSION_ID, doc_change) {
return await func.runtime.ui.refresh_document_changes_for_realtime_update(SESSION_ID, doc_change);
};
func.UI.screen.live_preview_hot_module_reload = async function (SESSION_ID, doc) {
return await func.runtime.ui.live_preview_hot_module_reload(SESSION_ID, doc);
};
func.UI.component = {};
func.UI.component.get_wrapped_nodes = function (target) {
if (!target) {
return [];
}
if (typeof target.toArray === "function") {
return target.toArray().filter(Boolean);
}
if (Array.isArray(target)) {
return target.filter(Boolean);
}
const first_node = func.runtime.ui.get_first_node(target);
return first_node ? [first_node] : [];
};
func.UI.component.move_wrapped_nodes = function (target, source) {
const target_node = func.runtime.ui.get_first_node(target);
if (!target_node) {
return false;
}
const nodes = func.UI.component.get_wrapped_nodes(source);
for (let index = 0; index < nodes.length; index++) {
target_node.appendChild(nodes[index]);
}
return true;
};
func.UI.component.create_app_modal_component = function (
SESSION_ID,
modal_content_name
) {
const root_element = SESSION_OBJ[SESSION_ID].root_element;
const xu_modal_controller_id = "xu-modal-controller";
var xu_modal_controller = root_element.querySelector(xu_modal_controller_id);
if (!xu_modal_controller) {
xu_modal_controller = document.createElement(xu_modal_controller_id);
root_element.prepend(xu_modal_controller);
}
customElements.define(
modal_content_name,
class extends HTMLElement {
constructor() {
super();
// console.log("modal_content_name", modal_content_name);
// let template = document.getElementById(
// "modal_template_" + modal_content_name
// );
// let templateContent = template.content;
// const shadowRoot = this.attachShadow({ mode: "open" });
// shadowRoot.appendChild(templateContent.cloneNode(true));
}
connectedCallback() {}
}
);
return xu_modal_controller_id;
};
// func.UI.component.create_in_app_modal_component = function (SESSION_ID) {
// customElements.define(
// "in-app-modal-content" + SESSION_ID,
// class ModalContent extends HTMLElement {
// connectedCallback() {
// func.UI.component.init_app_modal_component(
// SESSION_ID,
// "in-app-modal-content" + SESSION_ID
// );
// }
// }
// );
// };
// func.UI.component.create_qr_modal_component = function (SESSION_ID) {
// customElements.define(
// "qr-modal-content" + SESSION_ID,
// class ModalContent extends HTMLElement {
// connectedCallback() {
// func.UI.component.init_app_modal_component(
// SESSION_ID,
// "qr-modal-content" + SESSION_ID
// );
// }
// }
// );
// };
// func.UI.component.init_app_modal_component = async function (
// SESSION_ID,
// modal_content_name
// ) {
// const xu_modal_controller = document.querySelector("xu-modal-controller");
// var $modal = $(modal_content_name);
// var params =
// $(xu_modal_controller).data().xuControllerParams[modal_content_name];
// var icon;
// if (params.icon) {
// icon = await func.common.get_custom_icon(SESSION_ID, params.icon);
// }
// var dismiss = function () {
// APP_MODAL_OBJ[modal_content_name].dismiss().then(() => {
// APP_MODAL_OBJ[modal_content_name] = null;
// if (params.$container) {
// func.UI.screen.validate_exit_events(
// SESSION_ID,
// params.$container.data().xuData.paramsP,
// null,
// function () {
// func.datasource.clean_all(SESSION_ID, params.dsSession);
// }
// );
// }
// });
// };
// if (params.$container) {
// $modal.attr("id", params.$container.attr("id"));
// $.each(params.$container.data(), function (key, val) {
// $modal.data(key, val);
// });
// }
// var $header = $("<ion-header>");
// var $toolbar = $("<ion-toolbar>").appendTo($header);
// var $title = $("<ion-title>").text(params.menuTitle).appendTo($toolbar);
// if (icon) {
// var logo = `
// <img style="width: 45px; margin: 0 15px;" slot="start" src="${icon}" >
// `;
// $toolbar.append(logo);
// }
// if (
// !params.$container ||
// params.$container.data().xuData.paramsP.screenInfo.mCLS !== "false"
// ) {
// var $buttons = $('<ion-buttons slot="end">').appendTo($toolbar);
// $('<ion-button><ion-icon name="close"></ion-icon>')
// .click(dismiss)
// .appendTo($buttons);
// }
// if (
// !params.$container ||
// params.$container.data().xuData.paramsP.screenInfo.mHDR !== "false"
// ) {
// $header.appendTo($modal);
// }
// $modal.append(params.$dialogDiv);
// if (params.$modal_footer) {
// params.$modal_footer.appendTo($modal);
// }
// };
func.UI.component.create_app_page_component = async function (SESSION_ID, id) {
const component_id = "xu-page-component-" + id;
customElements.define(
component_id,
class extends HTMLElement {
constructor() {
super();
var xu_nav = SESSION_OBJ[SESSION_ID].root_element.querySelector("xu-nav");
var params = func.runtime.ui.get_data(xu_nav);
var container_data = func.runtime.ui.get_data(params.xuData.nav_params[id].$container);
for (const [key, val] of Object.entries(container_data)) {
func.runtime.ui.set_data(this, key, val);
}
}
connectedCallback() {
const page_back_callback = async () => {
await func.runtime.ui.validate_exit_events(
SESSION_ID,
func.runtime.ui.get_data(params.$container).xuData.paramsP,
null
);
nav.back();
func.datasource.clean_all(SESSION_ID, params.dsSession);
};
const nav = SESSION_OBJ[SESSION_ID].root_element.querySelector("xu-nav");
const params = func.runtime.ui.get_data(nav).xuData.nav_params[id];
params.callback(this, page_back_callback);
}
}
);
return component_id;
};
// func.UI.component.init_app_page_component = async function (SESSION_ID, id) {
// var params = $(SESSION_OBJ[SESSION_ID].root_element).find("xu-nav").data()
// .xuData.nav_params[id];
// var _this = this;
// var $page = $("xu-page-component-" + id).attr(
// "id",
// params.$container.attr("id")
// );
// $.each(params.$container.data(), function (key, val) {
// $page.data(key, val);
// });
// var icon;
// if (params.icon) {
// icon = await func.common.get_custom_icon(SESSION_ID, params.icon);
// }
// var $header = $("<ion-header>");
// var $toolbar = $("<ion-toolbar>").appendTo($header);
// $("<ion-title>").text(params.name).appendTo($toolbar);
// if (icon) {
// var logo = `
// <img style="width: 45px; margin: 0 15px;" slot="end" src="${icon}" >
// `;
// $toolbar.append(logo);
// }
// var $buttons = $('<ion-buttons slot="start">').appendTo($toolbar);
// if (params.$container.data().xuData.paramsP.screenInfo.mBCK !== "false") {
// var $button = $("<ion-back-button>");
// $button.off("click");
// $button
// .on("click", function () {
// func.UI.screen.validate_exit_events(
// SESSION_ID,
// params.$container.data().xuData.paramsP,
// null,
// function () {
// func.datasource.clean_all(SESSION_ID, params.dsSession);
// }
// );
// })
// .appendTo($buttons);
// }
// if (
// !params.$container ||
// params.$container.data().xuData.paramsP.screenInfo.mHDR !== "false"
// ) {
// if (
// params.icon ||
// params.name ||
// params.$container.data().xuData.paramsP.screenInfo.mBCK !== "false"
// ) {
// $header.appendTo($page);
// }
// }
// $page.append(params.div);
// };
func.UI.component.create_app_root_component = function (SESSION_ID) {
customElements.define(
"xu-root-component-" + SESSION_ID,
class ModalContent extends HTMLElement {
connectedCallback() {
const xu_nav = SESSION_OBJ[SESSION_ID].root_element.querySelector("xu-nav");
const xu_nav_data = func.runtime.ui.get_data(xu_nav);
func.UI.component.move_wrapped_nodes(this, xu_nav_data?.xuData?.$div);
const root_component_callback = xu_nav_data.xuData.root_component_callback;
if (root_component_callback) {
root_component_callback();
}
}
}
);
};
func.UI.component.create_app_popover_component = function (SESSION_ID) {
const root_element = SESSION_OBJ[SESSION_ID].root_element;
const xu_popover_controller_id = "xu-popover-controller-" + SESSION_ID;
var xu_popover_controller_el = root_element.querySelector(xu_popover_controller_id);
if (!xu_popover_controller_el) {
xu_popover_controller_el = document.createElement(xu_popover_controller_id);
root_element.prepend(xu_popover_controller_el);
}
customElements.define(
"xu-popover-content-" + SESSION_ID,
class ModalContent extends HTMLElement {
connectedCallback() {
const xu_popover_controller = document.querySelector(
"xu-popover-controller-" + SESSION_ID
);
var params = func.runtime.ui.get_data(xu_popover_controller, "xuControllerParams");
var popover_el = document.querySelector("xu-popover-content-" + SESSION_ID);
popover_el.setAttribute("id", func.runtime.ui.get_attr(params.$container, "id"));
var container_data = func.runtime.ui.get_data(params.$container);
for (const [key, val] of Object.entries(container_data)) {
func.runtime.ui.set_data(popover_el, key, val);
}
popover_el.innerHTML = '';
func.UI.component.move_wrapped_nodes(popover_el, params.$dialogDiv);
}
}
);
return xu_popover_controller_id;
};
func.UI.component.create_camera_select_popover_component = function (
SESSION_ID
) {
customElements.define(
"popover-camera-select-page" + SESSION_ID,
class ModalContent extends HTMLElement {
connectedCallback() {
const xu_popover_controller = document.querySelector(
"ion-popover-controller"
);
var params = func.runtime.ui.get_data(xu_popover_controller, "xuControllerParams");
func.UI.component.move_wrapped_nodes(this, params.$dialogDiv);
}
}
);
};
// func.UI.component.get_image_element = function (SESSION_ID, key, val, ds_id) {
// var $pallet_wrapper = $(`<div class="image_wrapper">`);
// var $modal_content = $(`<ion-content fullscreen scroll-x >`);
// var $modal_footer = $(`<ion-tab-bar slot="bottom">`);
// if (val.doc.dFWD) {
// $pallet_wrapper.css("width", val.doc.dFWD);
// }
// if (val.doc.dFHT) {
// $pallet_wrapper.css("height", val.doc.dFHT);
// }
// var $upload_wrapper = $(`<div class="upload_wrapper">`).appendTo(
// $pallet_wrapper
// );
// var $crop_wrapper = $(`<div>`).appendTo($modal_content);
// var $upload_image = $(
// `<img class="image_img" id="${key}" onerror="this.style.display='none'" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iNTBweCIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDYwIDYwIiBzdHlsZT0iIiB4bWw6c3BhY2U9InByZXNlcnZlIiBoZWlnaHQ9IjUwcHgiPgo8ZyBzdHlsZT0iJiMxMDsgICAgZmlsbDogcmdiYSgwLDAsMCwwLjEpOyYjMTA7Ij4KCTxwYXRoIGQ9Ik01NS4yMDEsMTUuNWgtOC41MjRsLTQtMTBIMTcuMzIzbC00LDEwSDEydi01SDZ2NUg0Ljc5OUMyLjE1MiwxNS41LDAsMTcuNjUyLDAsMjAuMjk5djI5LjM2OCAgIEMwLDUyLjMzMiwyLjE2OCw1NC41LDQuODMzLDU0LjVoNTAuMzM0YzIuNjY1LDAsNC44MzMtMi4xNjgsNC44MzMtNC44MzNWMjAuMjk5QzYwLDE3LjY1Miw1Ny44NDgsMTUuNSw1NS4yMDEsMTUuNXogTTgsMTIuNWgydjNIOCAgIFYxMi41eiBNNTgsNDkuNjY3YzAsMS41NjMtMS4yNzEsMi44MzMtMi44MzMsMi44MzNINC44MzNDMy4yNzEsNTIuNSwyLDUxLjIyOSwyLDQ5LjY2N1YyMC4yOTlDMiwxOC43NTYsMy4yNTYsMTcuNSw0Ljc5OSwxNy41SDZoNiAgIGgyLjY3N2w0LTEwaDIyLjY0Nmw0LDEwaDkuODc4YzEuNTQzLDAsMi43OTksMS4yNTYsMi43OTksMi43OTlWNDkuNjY3eiIvPgoJPHBhdGggZD0iTTMwLDE0LjVjLTkuOTI1LDAtMTgsOC4wNzUtMTgsMThzOC4wNzUsMTgsMTgsMThzMTgtOC4wNzUsMTgtMThTMzkuOTI1LDE0LjUsMzAsMTQuNXogTTMwLDQ4LjVjLTguODIyLDAtMTYtNy4xNzgtMTYtMTYgICBzNy4xNzgtMTYsMTYtMTZzMTYsNy4xNzgsMTYsMTZTMzguODIyLDQ4LjUsMzAsNDguNXoiLz4KCTxwYXRoIGQ9Ik0zMCwyMC41Yy02LjYxNywwLTEyLDUuMzgzLTEyLDEyczUuMzgzLDEyLDEyLDEyczEyLTUuMzgzLDEyLTEyUzM2LjYxNywyMC41LDMwLDIwLjV6IE0zMCw0Mi41Yy01LjUxNCwwLTEwLTQuNDg2LTEwLTEwICAgczQuNDg2LTEwLDEwLTEwczEwLDQuNDg2LDEwLDEwUzM1LjUxNCw0Mi41LDMwLDQyLjV6Ii8+Cgk8cGF0aCBkPSJNNTIsMTkuNWMtMi4yMDYsMC00LDEuNzk0LTQsNHMxLjc5NCw0LDQsNHM0LTEuNzk0LDQtNFM1NC4yMDYsMTkuNSw1MiwxOS41eiBNNTIsMjUuNWMtMS4xMDMsMC0yLTAuODk3LTItMnMwLjg5Ny0yLDItMiAgIHMyLDAuODk3LDIsMlM1My4xMDMsMjUuNSw1MiwyNS41eiIvPgo8L2c+CgogCgo8L3N2Zz4=" alt="" />`
// ).appendTo($upload_wrapper);
// var currentPopover = null;
// const open_select_camera_popup = async function () {
// const popoverController_div = document.querySelector(
// "ion-popover-controller"
// );
// const $list = $("<ion-list>");
// const $camera = $(
// `<ion-item>Take Photo <ion-icon name="camera-outline" slot="end"></ion-icon> </ion-icon></ion-item>`
// ).appendTo($list);
// const $photo = $(
// `<ion-item>Photo Library <ion-icon name="image-outline" slot="end"></ion-icon></ion-item>`
// ).appendTo($list);
// $camera.click(async function () {
// await popoverController.dismiss();
// navigator.camera.getPicture(
// function cameraSuccess(imageUri) {
// Doka.create()
// .edit(window.Ionic.WebView.convertFileSrc(imageUri))
// .then((output) => {
// // Called when the source image has been edited
// // Receives the output file and optionally, if `outputData`
// // is set to true, the editor state
// // console.log(output);
// var getBase64 = function (file) {
// var reader = new FileReader();
// reader.readAsDataURL(file);
// reader.onload = function () {
// $imgInp
// .data("filepond_base64", reader.result)
// .trigger("change");
// $upload_image.prop("src", reader.result);
// };
// reader.onerror = function (error) {
// console.log("Error: ", error);
// };
// };
// getBase64(output.file);
// });
// // You may choose to copy the picture, save it somewhere, or upload.
// },
// function cameraError(error) {
// console.debug("Unable to obtain picture: " + error, "app");
// },
// {
// quality: 100,
// correctOrientation: true,
// }
// );
// });
// $photo.click(async function () {
// await popoverController.dismiss();
// navigator.camera.getPicture(
// function cameraSuccess(imageUri) {
// Doka.create()
// .edit(window.Ionic.WebView.convertFileSrc(imageUri))
// .then((output) => {
// // Called when the source image has been edited
// // Receives the output file and optionally, if `outputData`
// // is set to true, the editor state
// var getBase64 = function (file) {
// var reader = new FileReader();
// reader.readAsDataURL(file);
// reader.onload = function () {
// $imgInp
// .data("filepond_base64", reader.result)
// .trigger("change");
// $upload_image.prop("src", reader.result);
// };
// reader.onerror = function (error) {
// console.log("Error: ", error);
// };
// };
// getBase64(output.file);
// });
// // You may choose to copy the picture, save it somewhere, or upload.
// },
// function cameraError(error) {
// console.debug("Unable to obtain picture: " + error, "app");
// },
// {
// quality: 100,
// correctOrientation: true,
// sourceType: Camera.MediaType.PICTURE,
// }
// );
// // }
// });
// $(popoverController_div).data({ params: { $dialogDiv: $list } });
// var popover = await popoverController.create({
// component: "popover-camera-select-page",
// // event: ev,
// translucent: true,
// });
// currentPopover = popover;
// return popover.present();
// };
// const croppie_integration = function () {
// function readURL(input) {
// if (input.files && input.files[0]) {
// var reader = new FileReader();
// reader.onload = function (e) {
// img_src = e.target.result;
// Doka.create()
// .edit(img_src)
// .then((output) => {
// // Called when the source image has been edited
// // Receives the output file and optionally, if `outputData`
// // is set to true, the editor state
// var getBase64 = function (file) {
// var reader = new FileReader();
// reader.readAsDataURL(file);
// reader.onload = function () {
// $imgInp.data("filepond_base64", reader.result);
// // .trigger("change");
// $upload_image.prop("src", reader.result);
// $upload_image.trigger("image_change");
// };
// reader.onerror = function (error) {
// console.log("Error: ", error);
// };
// };
// getBase64(output.file);
// });
// //////////////
// };
// reader.onloadend = function () {};
// reader.readAsDataURL(input.files[0]);
// }
// }
// $imgInp.change(function () {
// readURL(this);
// });
// };
// var $imgInp = $(
// `<input accept="file" type="file" class="imgInp" name="${key}" nodeid="${val.id}" recordid="${SESSION_OBJ[SESSION_ID].DS_GLB[ds_id].currentRecordId}"/>`
// )
// .attr("ui_id", key)
// .appendTo($upload_wrapper);
// if (func.utils.get_device()) {
// $imgInp.attr("hidden", true);
// $upload_image.click(function () {
// open_select_camera_popup();
// });
// } else {
// croppie_integration();
// }
// setTimeout(function () {
// $imgInp.css("height", $pallet_wrapper.height());
// }, 1000);
// return $pallet_wrapper;
// };
func.UI.component.init_xu_nav = function ($container, $nav) {
const container_el = func.runtime.ui.get_first_node($container);
const nav_el = func.runtime.ui.get_first_node($nav);
func.runtime.ui.set_data(nav_el, "xuData", { nav_stack: [] });
Object.defineProperty(nav_el, "setRoot", {
value: async function (component_id) {
return new Promise(async function (resolve, reject) {
func.runtime.ui.get_data(nav_el).xuData.nav_stack.unshift(component_id);
container_el.insertAdjacentHTML("beforeend", "<" + component_id + ">");
await customElements.whenDefined(component_id);
resolve();
});
},
configurable: true,
});
Object.defineProperty(nav_el, "push", {
value: async function (component_id) {
return new Promise(async function (resolve, reject) {
let last_component = func.runtime.ui.get_data(nav_el).xuData.nav_stack.at(-1);
var last_el = document.querySelector(last_component);
if (last_el) last_el.style.display = 'none';
func.runtime.ui.get_data(nav_el).xuData.nav_stack.push(component_id);
container_el.insertAdjacentHTML("beforeend", "<" + component_id + ">");
await customElements.whenDefined(component_id);
resolve();
});
},
configurable: true,
});
Object.defineProperty(nav_el, "popToRoot", {
value: async function () {
return new Promise(async function (resolve, reject) {
let last_component = func.runtime.ui.get_data(nav_el).xuData.nav_stack.at(-1);
var last_el = document.querySelector(last_component);
if (last_el) last_el.remove();
let root_component = func.runtime.ui.get_data(nav_el).xuData.nav_stack[0];
func.runtime.ui.get_data(nav_el).xuData.nav_stack[root_component];
var root_el = document.querySelector(root_component);
if (root_el) root_el.style.display = '';
await customElements.whenDefined(root_component);
resolve();
});
},
configurable: true,
});
Object.defineProperty(nav_el, "popTo", {
value: async function (index) {
return new Promise(async function (resolve, reject) {
let last_component = func.runtime.ui.get_data(nav_el).xuData.nav_stack.at(-1);
let last_component_index = func.runtime.ui.get_data(nav_el).xuData.nav_stack.length - 1;
var last_el = document.querySelector(last_component);
if (last_el) last_el.remove();
try {
var selected_component =
func.runtime.ui.get_data(nav_el).xuData.nav_stack[index || last_component_index - 1];
} catch (error) {
console.error(error);
return reject(error);
}
var selected_el = document.querySelector(selected_component);
if (selected_el) selected_el.style.display = '';
func.runtime.ui.get_data(nav_el).xuData.nav_stack.splice(last_component_index, 1);
await customElements.whenDefined(selected_component);
resolve();
});
},
configurable: true,
});
Object.defineProperty(nav_el, "back", {
value: async function (index) {
return new Promise(async function (resolve, reject) {
let last_component = func.runtime.ui.get_data(nav_el).xuData.nav_stack.at(-1);
let last_component_index = func.runtime.ui.get_data(nav_el).xuData.nav_stack.length - 1;
if (last_component_index === 0) return;
var last_el = document.querySelector(last_component);
if (last_el) last_el.remove();
try {
var selected_component =
func.runtime.ui.get_data(nav_el).xuData.nav_stack[last_component_index - 1];
} catch (error) {
console.error(error);
return reject(error);
}
var selected_el = document.querySelector(selected_component);
if (selected_el) selected_el.style.display = '';
func.runtime.ui.get_data(nav_el).xuData.nav_stack.splice(last_component_index, 1);
await customElements.whenDefined(selected_component);
resolve();
});
},
configurable: true,
});
};
func.UI.main = {};
func.UI.main.clear_SYNC_INTERVAL = function () {
// no-op — idle timeout listeners now managed natively
};
func.UI.main.embed_prog_execute = async function (SESSION_ID, prog) {
var _session = SESSION_OBJ[SESSION_ID];
// await func.utils.TREE_OBJ.get(SESSION_ID, prog);
const _prog = await func.utils.VIEWS_OBJ.get(SESSION_ID, prog);
const get_params_obj = function () {
// get in parameters
var params_obj = {};
if (_prog?.properties?.progParams) {
for (const [key, val] of Object.entries(_prog.properties.progParams)) {
// if (val.data.dir !== 'in') continue;
if (typeof _session.url_params?.[val.data.parameter] !== 'undefined') {
params_obj[val.data.parameter] = _session.url_params?.[val.data.parameter];
continue;
}
console.warn(`Warning: Program ${_prog.properties.menuName} expected In parameter: ${val.data.parameter} but received null instead`);
}
}
return params_obj;
};
let screen_ret = await func.utils.get_screen_obj(SESSION_ID, prog);
if (screen_ret) {
let ret_init = await func.runtime.ui.init_screen({
SESSION_ID,
prog_id: prog,
sourceScreenP: null,
callingDataSource_objP: null,
$callingContainerP: func.runtime.ui.get_session_root(SESSION_ID),
triggerIdP: null,
rowIdP: null,
jobNoP: null,
is_panelP: null,
parameters_obj_inP: get_params_obj(),
source_functionP: 'call_embed',
});
func.runtime.platform.set_title(screen_ret.properties.menuTitle);
return;
}
console.error('Program not exist', prog);
func.UI.utils.progressScreen.show(SESSION_ID, 'Program not exist', null, true);
// }
};
func.UI.main.embed_loader = async function (SESSION_ID) {
var _session = SESSION_OBJ[SESSION_ID];
const platform = func.runtime.platform;
const browser_hash = platform.get_url_hash();
var hash = '';
if (browser_hash) hash = browser_hash.substr(1);
_session.SYS_GLOBAL_STR_BROWSER_HASH_ID = hash;
_session.SYS_GLOBAL_STR_BROWSER_TITLE = platform.get_document()?.title || '';
const init_system_ds = async function () {
if (!['main'].includes(_session.opt.app_computing_mode)) {
await func.index.new_webworker(SESSION_ID, { menuName: 'Main' });
// await func.index.call_worker(SESSION_ID, {
// service: "create_webworker_globals",
// data: { ds_data: _session.DS_GLB[0], session_id: SESSION_ID },
// });
}
const ret = await func.datasource.create(SESSION_ID, 'system');
/////////// moved to the datasource callback to allow sync on_load events to run at worker
// if (!["main"].includes(_session.opt.app_computing_mode)) {
// // await func.index.new_webworker(SESSION_ID, { menuName: "Main" });
// await func.index.call_worker(SESSION_ID, {
// service: "create_webworker_globals",
// data: { ds_data: _session.DS_GLB[0], session_id: SESSION_ID },
// });
// }
func.index.set_ds_0_proxy(SESSION_ID);
return ret;
// });
};
const set_SYS_GLOBAL_KEYS_STATE = async function (SESSION_ID, e, state) {
if (!_session?.DS_GLB?.[0]) return;
if (e.keyCode !== 16 && e.keyCode !== 17 && e.keyCode !== 18 && e.keyCode !== 91) {
return;
}
var data = {};
if (e.keyCode === 17) {
data.SYS_GLOBAL_BOL_CONTROL_KEY_STATE = state;
}
if (e.keyCode === 16) {
data.SYS_GLOBAL_BOL_SHIFT_KEY_STATE = state;
}
if (e.keyCode === 18) {
data.SYS_GLOBAL_BOL_ALT_KEY_STATE = state;
}
if (e.keyCode === 91) {
data.SYS_GLOBAL_BOL_COMMAND_KEY_STATE = state;
}
var datasource_changes = {
[0]: {
['data_system']: data,
},
};
await func.datasource.update(SESSION_ID, datasource_changes);
};
const start_workers = async function () {
_session.WORKER_OBJ.fx = new func.utils.job_worker(SESSION_ID);
_session.WORKER_OBJ.fx.init();
};
const create_embed_container = async function () {
func.runtime.ui.ensure_embed_container(SESSION_ID);
};
const execute_PENDING_OPEN_URL_EVENTS = async function () {
if (typeof func.events.execute_PENDING_OPEN_URL_EVENTS !== 'undefined' && glb.is_cordova) {
func.events.execute_PENDING_OPEN_URL_EVENTS();
}
};
const remove_loader = async function () {
document.querySelectorAll('.loader').forEach(function (el) { el.remove(); });
const root_node = func.runtime.ui.get_first_node(_session.root_element);
if (root_node?.classList) {
root_node.classList.remove('loader_background_color');
}
};
const perform_callback = async function () {
if (_session.api_callback) {
_session.api_callback('xuda_ready', SESSION_ID, SESSION_OBJ);
}
};
const call_program = async function () {
if (_session.route_id) {
const route_obj = await func.utils.DOCS_OBJ.get(SESSION_ID, _session.route_id);
function flattenMenuItems(menu) {
let flatMenu = {};
function recurse(items) {
for (let item of items) {
flatMenu[item.id] = item;
if (item.children && item.children.length > 0) {
recurse(item.children);
}
}
}
recurse(menu);
return flatMenu;
}
const flatMenu = flattenMenuItems(route_obj.routeMenu.menu);
const menu_obj = flatMenu[_session.menu_id];
if (_session.menu_id) {
if (menu_obj) {
_session.prog_id = menu_obj.prog_id;
if (menu_obj.prog_params) {
_session.url_params = {
..._session.url_params,
...menu_obj.prog_params,
};
}
if (menu_obj.global_params) {
_session.url_params = {
..._session.url_params,
...menu_obj.global_params,
};
}
}
}
}
if (!_session.prog_id) return;
await func.utils.TREE_OBJ.get(SESSION_ID, _session.prog_id);
let screen_ret = await func.utils.get_screen_obj(SESSION_ID, _session.prog_id);
if (screen_ret) {
func.UI.main.embed_prog_execute(SESSION_ID, _session.prog_id);
} else {
console.error('Program not exist', _session.prog_id);
func.UI.utils.progressScreen.show(SESSION_ID, 'Program not exist', null, true);
}
};
const register_run_background_plugins = async function () {
if (typeof glb.SLIM_BUNDLE !== 'undefined' || glb.SLIM_BUNDLE) return;
for await (const [plugin_name, val] of Object.entries(APP_OBJ[_session.app_id].app_plugins_purchased)) {
if (val.installed && val.run_in_background && val.manifest?.['runtime.mjs']?.exist) {
try {
const plugin_runtime_src = await func.utils.get_plugin_npm_cdn(SESSION_ID, plugin_name, `${val.manifest['runtime.mjs'].dist ? 'dist/' : ''}runtime.mjs`);
if (val.manifest['runtime.mjs'].dist && val.manifest?.['runtime.mjs']?.css) {
const plugin_runtime_css_url = await func.utils.get_plugin_npm_cdn(SESSION_ID, plugin_name, 'dist/runtime.css');
func.utils.load_css_on_demand(plugin_runtime_css_url);
}
const plugin_script = await import(plugin_runtime_src);
eval(plugin_script);
let plugin_setup_script_ret = null;
if (val.manifest?.['index.mjs']?.exist) {
const plugin_setup_src = await func.utils.get_plugin_npm_cdn(SESSION_ID, plugin_name, `${val.manifest['index.mjs'].dist ? 'dist/' : ''}index.mjs`);
let plugin_setup_script = await import(plugin_setup_src);
if (plugin_setup_script) {
plugin_setup_script_ret = await func.utils.get_plugin_setup(SESSION_ID, plugin_name);
if (plugin_setup_script_ret.code < 0) {
throw plugin_setup_script_ret;
}
}
}
glb.lifecycle.plugins[plugin_name] = {
plugin_script,
setup_data: plugin_setup_script_ret?.data,
};
} catch (err) {
console.error(err);
}
}
}
};
async function updateOnlineStatus() {
if (!_session?.DS_GLB?.[0]) return;
var data = {};
if (IS_ONLINE) {
data.SYS_GLOBAL_BOL_ONLINE = 1;
} else {
data.SYS_GLOBAL_BOL_ONLINE = 0;
}
var datasource_changes = {
[0]: {
['data_system']: data,
},
};
await func.datasource.update(SESSION_ID, datasource_changes);
}
await register_run_background_plugins();
func.runtime.ui.show_root_element(SESSION_ID);
func.UI.component.create_app_root_component(SESSION_ID);
await glb.lifecycle.execute(SESSION_ID, 'beforeInit');
await start_workers();
await init_system_ds();
await glb.lifecycle.execute(SESSION_ID, 'initialized');
await create_embed_container();
await execute_PENDING_OPEN_URL_EVENTS();
await remove_loader();
await func.UI.worker.init(SESSION_ID);
await glb.lifecycle.execute(SESSION_ID, 'beforeMounted');
await call_program();
await glb.lifecycle.execute(SESSION_ID, 'mounted');
await perform_callback();
func.utils.debug.write(SESSION_ID, 'Xuda.ai started.');
await glb.lifecycle.execute(SESSION_ID, 'systemReady');
/////////////////////////////
// bridge DOM keyboard events into platform event bus
if (func.runtime.platform.has_document()) {
document.addEventListener('keydown', function (e) {
func.runtime.platform.emit('keydown', e);
});
document.addEventListener('keyup', function (e) {
func.runtime.platform.emit('keyup', e);
});
}
func.runtime.platform.on('keydown', function (e) {
set_SYS_GLOBAL_KEYS_STATE(SESSION_ID, e, 1);
});
func.runtime.platform.on('keyup', function (e) {
set_SYS_GLOBAL_KEYS_STATE(SESSION_ID, e, 0);
});
await updateOnlineStatus();
platform.add_window_listener('online', updateOnlineStatus);
platform.add_window_listener('offline', updateOnlineStatus);
console.log('xuda.ai system ready.');
platform.dispatch_body_event(glb.system_ready_event);
};
func.UI.main.set_custom_css = function (SESSION_ID, callbackP) {
const css = SESSION_OBJ[SESSION_ID].DS_GLB[0].data_system?.['SYS_GLOBAL_STR_SITE_CSS'];
if (css) {
func.runtime.platform.inject_css(css);
}
callbackP();
};
func.index = {};
////////////////////////////////////
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
func.index.init_document_listeners();
func.UI.utils.indicator.worker.normal();
func.index.init_service_workers();
});
} else {
setTimeout(function () {
func.index.init_document_listeners();
func.UI.utils.indicator.worker.normal();
func.index.init_service_workers();
}, 0);
}
function xuda(...args) {
const platform = func.runtime.platform;
const runtime_location = platform.get_location();
let element = null;
let opt = {};
let callback = null;
const report_bootstrap_issue = function (type, message) {
if (func.utils?.report_issue) {
func.utils.report_issue('', {
code: 'RUN_MSG_GEN_010',
title: 'Runtime Bootstrap Configuration Error',
source: 'xuda',
message,
type,
skip_log: true,
});
return;
}
const console_method = type === 'W' ? 'warn' : type === 'I' ? 'log' : 'error';
console[console_method](`XUDA ${console_method.toUpperCase()} RUN_MSG_GEN_010`, message);
};
for (const arg of args) {
if (platform.is_html_element(arg)) {
// Detect the element (e.g., <div>, <span>, etc.)
element = arg;
} else if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) {
// Detect options (non-null object that's not an array)
opt = arg;
} else if (typeof arg === 'function') {
// Detect the callback function
callback = arg;
}
}
if (!element) {
if (typeof glb.SLIM_BUNDLE === 'undefined' || !glb.SLIM_BUNDLE) {
report_bootstrap_issue('E', 'Xuda Error - element argument is empty');
return;
}
element = 'body';
report_bootstrap_issue('W', 'root element set to body');
}
if (typeof element === 'string' ? !document.querySelector(element) : !element) {
report_bootstrap_issue('E', 'Xuda Error - element not found');
return;
}
if (typeof opt === 'undefined') {
report_bootstrap_issue('E', 'Xuda Error - opt argument is undefined');
return;
}
if (typeof opt !== 'object') {
report_bootstrap_issue('E', 'Xuda Error - opt argument is not an object');
return;
}
if (!opt.ssr_payload && func.runtime.platform.get_window()?.__XUDA_SSR__) {
opt.ssr_payload = func.runtime.platform.get_window().__XUDA_SSR__;
}
func.runtime.render.apply_runtime_bootstrap_defaults(opt);
glb.URL_PARAMS = func.common.getJsonFromUrl(platform.get_url_href());
glb.worker_type = 'Worker';
if (opt.debug_js) {
glb.debug_js = true;
if ((runtime_location?.host?.includes('localhost') || runtime_location?.host?.includes('127.0.0.1')) && typeof glb.SLIM_BUNDLE === 'undefined' && typeof glb.CODE_BUNDLE === 'undefined') {
glb.worker_type = 'Dev';
} else {
glb.worker_type = 'Debug';
}
}
const call_xuda = async function () {
const _instance_id = Date.now().toString() + Math.round(Math.random() * 10000).toString();
const _api_callback = callback;
const create_index_html = function () {
func.runtime.ui.ensure_app_shell(SESSION_ID, _session.domain);
};
const device_ready = async function () {
return new Promise(function (resolve, reject) {
if (!func.utils.get_device()) {
return resolve();
}
platform.get_document().addEventListener(
'deviceready',
function () {
glb.is_cordova = true;
resolve();
},
false,
);
});
};
const get_fingerprint_component = async function () {
return new Promise(function (resolve, reject) {
Fingerprint2.get(function (components) {
resolve(components);
});
});
};
const get_session_id = function () {
return func.runtime.session.get_fingerprint(components, _instance_id);
};
const get_fingerprint = function () {
return func.runtime.session.get_fingerprint(components);
};
const init_SESSION = function () {
return func.runtime.session.create_state(SESSION_ID, {
opt,
root_element: typeof element === 'string' ? document.querySelector(element) : element,
worker_type: glb.worker_type,
api_callback: _api_callback,
code_bundle: glb.CODE_BUNDLE,
slim_bundle: glb.SLIM_BUNDLE,
url_params: opt.url_params,
});
};
const set_SESSION = async function () {
for await (const key of ['gtp_token', 'app_token', 'prog_id', 'domain', 'engine_mode', 'crawler', 'app_id', 'route_id', 'menu_id', 'local_live_preview', 'project_data']) {
_session[key] = func.UI.utils.get_url_attribute(SESSION_ID, key);
// set defaults
if (!_session[key]) {
func.runtime.session.set_default_value(_session, key);
}
// set exceptions
switch (key) {
case 'domain':
if (_session[key].includes('localhost') || _session[key].includes('127.0.0.1')) {
const getSubdomain = (url) => {
let domain = url;
if (url.includes('://')) {
domain = url.split('://')[1];
}
let subdomain = '';
if (!_session[key].includes('127.0.0.1')) {
subdomain = domain.split('.')[0];
}
return subdomain;
};
_session[key] = getSubdomain(_session[key]) ? getSubdomain(_session[key]) + '.xuda.ai' : 'xuda.ai';
}
break;
case 'project_data': {
if (typeof glb.SLIM_BUNDLE !== 'undefined' || glb.SLIM_BUNDLE) {
const { root_element } = _session;
if (!_session[key]) {
_session[key] = {};
}
if (!_session[key].programs) {
_session[key].programs = {};
}
if (!_session[key].globals) {
_session[key].globals = {
_id: 'globals',
progDataSource: {},
properties: { menuType: 'globals' },
studio_meta: {},
progEvents: [],
progFields: [],
};
}
const _templates = document.querySelectorAll('template');
if (_templates.length) {
const module = await func.common.get_module(SESSION_ID, 'xuda-cli-plugin-html-parser-module.esm.mjs');
_templates.forEach(function (el, idx) {
const _id = el.getAttribute('id') || 'template_' + idx.toString();
if (!_session[key].programs[_id]) {
_session[key].programs[_id] = {
_id,
progDataSource: {},
properties: { menuType: 'component', renderType: 'form' },
studio_meta: {},
progEvents: [],
progFields: [],
};
}
if (el.innerHTML) {
window.xudaStringify = module.xudaStringify;
const progUi = module.xudaPrase(el.innerHTML);
_session[key].programs[_id].progUi = progUi?.[0]?.children || [];
}
});
if (!_session.prog_id) {
_session.prog_id = _templates[0]?.getAttribute('id') || 'template_0';
}
root_element.innerHTML = '';
// if (!_session[key].programs._default) {
// _session[key].programs._default = {
// _id: "_default",
// progDataSource: {},
// properties: { menuType: "component", renderType: "form" },
// studio_meta: {},
// progEvents: [],
// progFields: [],
// };
// }
// if ($("template").html()) {
// const module = await func.common.get_module(
// SESSION_ID,
// "xuda-cli-plugin-html-parser-module.esm.js"
// );
// window.xudaStringify = module.xudaStringify;
// const progUi = module.xudaPrase($("template").html());
// const { root_element } = _session;
// $(root_element).empty();
// _session[key].programs._default.progUi =
// progUi?.[0]?.children || [];
// _session.prog_id = "_default";
// }
}
}
break;
}
default:
break;
}
}
// );
_session.crawler = opt.crawler;
};
const set_SYS_GLOBAL_OBJ_CLIENT_INFO = function () {
func.runtime.session.populate_client_info(_session, components);
};
const init_globals = function () {
const { app_id, worker_type } = _session;
if (worker_type !== 'Worker') {
glb.DEBUG_MODE = true;
}
SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal = -1;
PROJECT_OBJ[app_id] = {};
DOCS_OBJ[app_id] = {};
glb.APP_INFO[app_id] = {};
glb.ROOT_ELEMENT_ATTRIBUTES = func.UI.utils.get_root_element_attributes(SESSION_ID);
DATASOURCE_INTERVALS[SESSION_ID] = {};
// if (glb.DEBUG_MODE) glb.WORKER_TIMEOUT = 10000;
};
// const set_ViewUITreeObj_data = async function () {
// const { prog_id, project_data, root_element } = _session;
// if (!project_data) return;
// if (prog_id) {
// return;
// let tree_properties = project_data.programs[prog_id]?.properties;
// tree_properties.id = prog_id;
// tree_properties.renderType =
// tree_properties.flow === "multi_view" ? "grid" : "form";
// tree_properties.menuType = tree_properties.type;
// }
// // if ($(root_element).html()) {
// // const module = await func.common.get_module(
// // SESSION_ID,
// // "xuda-cli-plugin-html-parser-module.esm.js"
// // );
// // window.xudaStringify = module.xudaStringify;
// // const progUi = module.xudaPrase($(root_element).html());
// // $(root_element).empty();
// // const _id = "startup_program";
// // const d = Date.now();
// // DOCS_OBJ[_session.app_id][_id] = {
// // _id,
// // stat: 3,
// // docType: "studio",
// // docDate: d,
// // ts: d,
// // studio_meta: {
// // created: d,
// // parentId: "programs",
// // },
// // properties: {
// // progParams: [],
// // menuName: _id,
// // menuTitle: _id,
// // menuType: "component",
// // renderType: "form",
// // },
// // progEvents: [],
// // progFields: [],
// // progUi,
// // };
// // _session.prog_id = _id;
// // // console.log(code);
// // // if (prog_id) {
// // // project_data.programs[prog_id].progUi = [
// // // {
// // // children: code,
// // // tagName: root_element.tagName.toLowerCase(),
// // // type: "element",
// // // },
// // // ];
// // // }
// // }
// };
const print_version_info = function () {
const version_name = `Xuda runtime ${opt.engine_mode} ${opt?.app_version || ''} Session Id: ${SESSION_ID}`;
const divider = '#'.repeat(version_name.length);
console.info(divider);
console.info(version_name);
console.info(divider);
};
const print_xuda_banner = function () {
var banner = '';
['__ ___ _ ____ _ ', '\\ \\/ / | | | _ \\ / \\ ', ' \\ /| | | | | | |/ _ \\ ', ' / \\| |_| | |_| / ___ \\ ', '/_/\\_\\\\___/|____/_/ \\_\\'].forEach((e) => {
banner += e + '\r\n';
});
console.info(banner);
};
print_xuda_banner();
await func.index.checkConnectivity();
var SESSION_ID = Date.now();
var components;
if (typeof glb.SLIM_BUNDLE === 'undefined' || !glb.SLIM_BUNDLE) {
await device_ready();
components = await get_fingerprint_component();
SESSION_ID = get_session_id();
print_version_info();
}
var _session = init_SESSION();
await set_SESSION();
create_index_html();
APP_OBJ[_session.app_id] = {};
// _session.app_id = _session.app_id;
init_globals();
// await set_ViewUITreeObj_data();
if (typeof glb.SLIM_BUNDLE !== 'undefined' && glb.SLIM_BUNDLE) {
await func.UI.main.embed_loader(SESSION_ID);
return;
}
set_SYS_GLOBAL_OBJ_CLIENT_INFO(SESSION_ID);
if (!_session.app_id) {
func.UI.utils.progressScreen.show(SESSION_ID, 'Error reading app_id', false, true);
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_GEN_010',
source: 'xuda',
message: 'no app_id found',
type: 'E',
});
return;
}
const module = await func.common.get_module(SESSION_ID, 'xuda-project-loader-module.esm.js');
const db_adapter = await func.common.get_module(SESSION_ID, 'xuda-db-adapter-module.mjs');
func.db = db_adapter._db;
await module.project_loader(SESSION_ID, _session.app_id);
func.db.pouch.init_db_replication(SESSION_ID);
};
glb.system_ready_event = new Event('on_mounted');
call_xuda();
return {
on_mounted: function (fn) {
document.body.addEventListener(
'on_mounted',
(e) => {
fn();
},
false,
);
},
};
}
// func.index.on_mounted = async function (fn) {
// fn();
// };
// export const init = function (...args) {
// return xuda(args);
// };
func.index.call_worker = async function (SESSION_ID, obj, params, promiseP) {
var _session = SESSION_OBJ[SESSION_ID];
if (_session.opt.app_computing_mode === 'main') {
return;
}
return new Promise(async function (resolve, reject) {
var worker_id;
const set_promise_queue = function (worker_id) {
var t = glb.worker_queue_num++;
try {
func.runtime.workers.set_promise(SESSION_ID, worker_id, t, {
resolve: promiseP ? promiseP.resolve : resolve,
reject: promiseP ? promiseP.reject : reject,
worker_id,
});
} catch (e) {
console.log(worker_id);
}
return t;
};
const get_worker_id = function (ds) {
// return 1; // dismiss multi thread 2021 08 26
if (!_session.DS_GLB[ds]) {
if (ds == 0) {
func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_020',
source: 'func.index.call_worker',
message: 'Error - onscreen (window,modal etc..) event cannot be invoked by on_load',
type: 'E',
});
return;
} else {
// console.error("Error - worker not found");
return;
}
}
if (ds == 0) {
return 1;
}
if (_session.DS_GLB[ds].worker_id) {
return _session.DS_GLB[ds].worker_id;
} else {
if (typeof _session.DS_GLB[ds].parentDataSourceNo !== 'undefined') {
return get_worker_id(_session.DS_GLB[ds].parentDataSourceNo);
}
}
};
if (obj.service === 'datasource_create') {
if (!params || (params && !params.done)) {
var prog_obj = await func.utils.TREE_OBJ.get(SESSION_ID, obj.data.prog_id);
if (!prog_obj || !prog_obj.dedicatedWorker) {
// MAIN WORKER
if (!Object.keys(WEB_WORKER[SESSION_ID]).length) {
worker_id = await func.index.new_webworker(
SESSION_ID,
{
menuName: 'Main',
},
obj,
);
// return; //_resolve();
} else {
if (obj.data.IS_DATASOURCE_REFRESH) {
worker_id = get_worker_id(obj.data.parentDataSourceNo);
} else {
worker_id = get_worker_id(obj.data.parentDataSourceNoP);
}
if (!worker_id) {
}
}
} else {
// DEDICATE WORKER
if (obj.data.IS_DATASOURCE_REFRESH) {
worker_id = get_worker_id(obj.data.parentDataSourceNo);
} else {
worker_id = await func.index.new_webworker(SESSION, prog_obj, obj);
return _resolve();
}
}
} else {
worker_id = params.worker_id;
}
} else {
if (params && params.worker_id) {
worker_id = params.worker_id;
} else {
worker_id = get_worker_id(obj.data.dssession);
}
if (!worker_id) worker_id = 1; //return;
}
try {
var msg = obj;
msg.worker_id = worker_id;
if (!worker_id) {
console.warn('missing worker_id');
}
msg.promise_queue_id = set_promise_queue(worker_id);
if (!WEB_WORKER[SESSION_ID][worker_id]) {
// worker does not exist on globals on_load
return resolve();
}
msg = JSON.stringify(obj, func.utils.clean_stringify_null, '\t');
let msg_obj = JSON.parse(msg);
if (!func.runtime.workers.is_server_transport(_session)) {
msg_obj.data = JSON.stringify(msg_obj.data);
}
func.runtime.workers.send_message(SESSION_ID, worker_id, _session, msg_obj, WEBSOCKET_PROCESS_PID);
} catch (e) {
console.error(e);
return reject(e);
}
});
};
func.index.init_SCREEN_BLOCKER = async function (SESSION_ID) {
const get_loader = async function () {
if (func.runtime.session.is_slim(SESSION_ID) || typeof UI_FRAMEWORK_PLUGIN?.loader !== 'function') {
return {
dismiss: function () {},
};
}
return await UI_FRAMEWORK_PLUGIN.loader(LOADER_TEXT);
};
setInterval(async function () {
if (glb.CURRENT_APP_LOADING || (!LOADER_ACTIVE && xu_isEmpty(SCREEN_BLOCKER_OBJ))) {
return;
}
glb.CURRENT_APP_LOADING = 1;
const loader = await get_loader();
glb.CURRENT_APP_LOADING = loader;
var interval = setInterval(function () {
if (xu_isEmpty(SCREEN_BLOCKER_OBJ) && !LOADER_ACTIVE) {
if (glb.CURRENT_APP_LOADING) {
glb.CURRENT_APP_LOADING.dismiss();
}
clearInterval(interval);
glb.CURRENT_APP_LOADING = null;
}
}, 100);
}, 1000);
};
func.index.init_document_listeners = function () {
const platform = func.runtime.platform;
if (!func.index._global_error_listeners_attached) {
const report_global_error = function (payload) {
const SESSION_ID = Object.keys(SESSION_OBJ || {})[0] || '';
if (!func.utils?.report_issue) return;
func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_GEN_000',
source: payload.source,
message: payload.message,
type: payload.type || 'E',
err: payload.err,
details: payload.details,
skip_log: !SESSION_ID,
});
};
window.addEventListener('error', function (event) {
report_global_error({
source: 'window.error',
message: event.message || 'Unhandled window error',
err: event.error,
details: {
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
},
});
});
window.addEventListener('unhandledrejection', function (event) {
const reason = event.reason;
report_global_error({
code: 'RUN_MSG_GEN_020',
source: 'window.unhandledrejection',
message: reason?.message || func.utils._stringify_issue_message(reason) || 'Unhandled promise rejection',
err: reason instanceof Error ? reason : null,
details: {
reason_type: typeof reason,
reason: func.utils._serialize_issue_value(reason),
},
});
});
func.index._global_error_listeners_attached = true;
}
document.addEventListener('keydown', function (event) {
CLIENT_ACTIVITY_TS = Date.now();
var keys = {
72: {
key: 'h',
name: 'Help',
fx: function () {
let SESSION_ID = Object.keys(SESSION_OBJ)[0];
let _session = SESSION_OBJ[SESSION_ID];
let app_id = _session.app_id;
if (!_session.opt.enable_utility_screen) return;
func.UI.utils.progressScreen.show(
SESSION_ID,
`
<center>Utility Screen</center>
<div class="help_screen">
<button name="hard_reload">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" style="enable-background:new 0 0 473.677 473.677; xml:space="preserve" >
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
</svg><h3 style="text-align: center;display: flex;align-items: center;gap: 10px;">Clean cache & Reload <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path></svg></h3>
</button>
<button name="reset_worker">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-activity" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" style="enable-background:new 0 0 473.677 473.677; xml:space="preserve">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 12h4l3 8l4 -16l3 8h4"></path>
</svg>
<h3 style="text-align: center;display: flex;align-items: center;gap: 10px;">Reset worker<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path></svg></h3>
</button>
<button name="get_support">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-lifebuoy" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" xml:space="preserve">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"></path>
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"></path>
<path d="M15 15l3.35 3.35"></path>
<path d="M9 15l-3.35 3.35"></path>
<path d="M5.65 5.65l3.35 3.35"></path>
<path d="M18.35 5.65l-3.35 3.35"></path>
</svg>
<h3 style="text-align: center;display: flex;align-items: center;gap: 10px;">Get support ${
SUPPORT_PEER ? '(in session, click to cancel)' : ''
}<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path></svg></h3>
</button>
</div>
`,
);
setTimeout(() => {
const hardReloadBtn = document.querySelector('[name="hard_reload"]');
if (hardReloadBtn) {
hardReloadBtn.onclick = async function () {
await func.index.delete_pouch(SESSION_ID);
location.reload();
};
}
const resetWorkerBtn = document.querySelector('[name="reset_worker"]');
if (resetWorkerBtn) {
resetWorkerBtn.onclick = function () {
// let session_id = Object.keys(SESSION_OBJ)[0];
_session.WORKER_OBJ.jobs[0].stat = null;
_session.WORKER_OBJ.stat = null;
func.UI.utils.progressScreen.hide(SESSION_ID);
};
}
const getSupportBtn = document.querySelector('[name="get_support"]');
if (getSupportBtn) {
getSupportBtn.onclick = async function () {
const get_support = await func.common.get_module(SESSION_ID, 'xuda-get-support-module.esm.js');
var name, subject;
const prompt_name = () => {
name = window.prompt(`Get support from your ${APP_OBJ[app_id].app_general_prop?.app_name} team, \n\nYour Name (required):`);
if (name === '') prompt_name();
};
const prompt_subject = () => {
subject = window.prompt(`When you click on "Ok," you are granting permission to ${APP_OBJ[app_id]?.app_name} team to access your browser. \n\nSubject (required):`);
if (subject === '') prompt_subject();
};
if (!SUPPORT_PEER) {
// if (
// typeof firebase !== "undefined" &&
// firebase?.auth()?.currentUser?.displayName
// ) {
// name = firebase.auth().currentUser.displayName;
// } else {
// prompt_name();
// }
try {
name = firebase.auth().currentUser.displayName;
} catch (error) {
if (_session?.USR_OBJ?.usr_name) {
name = _session.USR_OBJ.usr_name;
} else {
prompt_name();
}
}
if (name === null) return;
prompt_subject();
if (subject === null) return;
func.common.db(SESSION_ID, 'get_support', {
name,
subject,
});
setTimeout(() => {
document.body.classList.add('get_support_request');
const oldTitle = document.body.querySelector('.get_support_request_title');
if (oldTitle) oldTitle.remove();
const titleDiv = document.createElement('div');
titleDiv.className = 'get_support_request_title';
titleDiv.textContent = 'Support pending';
document.body.appendChild(titleDiv);
}, 2000);
// get_support.init_peer(SESSION_ID, name, subject);
} else {
get_support.terminate_peer(SESSION_ID);
}
func.UI.utils.progressScreen.hide(SESSION_ID);
};
}
}, 1000);
},
},
};
if (event.which === 27) {
// esc
let SESSION_ID = Object.keys(SESSION_OBJ)[0];
func.UI.utils.progressScreen.hide(SESSION_ID);
}
if (event.ctrlKey && event.shiftKey) {
if (keys[event.which]) {
keys[event.which].fx();
}
// else {
// // func.utils.alerts.toast("Invalid Key", "error");
// }
}
});
document.addEventListener('mousemove', function (e) {
CLIENT_ACTIVITY_TS = Date.now();
posX = e.pageX;
posY = e.pageY;
if (SESSION_OBJ[SESSION_ID]?.DS_GLB?.[0]) {
SESSION_OBJ[SESSION_ID].DS_GLB[0].data_system['SYS_GLOBAL_OBJ_CLIENT_INFO'].cursor_pos_x = posX;
SESSION_OBJ[SESSION_ID].DS_GLB[0].data_system['SYS_GLOBAL_OBJ_CLIENT_INFO'].cursor_pos_y = posY;
}
// }
}); //get pointer position
func.index.makeDraggable = function (el) {
CLIENT_ACTIVITY_TS = Date.now();
var isFixed = getComputedStyle(el).position === 'fixed',
adjX = 0,
adjY = 0;
var _moveHandler, _upHandler;
el.addEventListener('mousedown', function (ev) {
var rect = el.getBoundingClientRect();
if (isFixed) {
adjX = window.scrollX;
adjY = window.scrollY;
}
var ox = ev.pageX - (rect.left + window.scrollX),
oy = ev.pageY - (rect.top + window.scrollY);
var dragOffset = { x: ox, y: oy };
_moveHandler = function (ev) {
ev.preventDefault();
ev.stopPropagation();
if (isFixed) {
adjX = window.scrollX;
adjY = window.scrollY;
}
el.style.left = ev.pageX - adjX - dragOffset.x + 'px';
el.style.top = ev.pageY - adjY - dragOffset.y + 'px';
};
_upHandler = function () {
window.removeEventListener('mousemove', _moveHandler);
window.removeEventListener('mouseup', _upHandler);
};
window.addEventListener('mousemove', _moveHandler);
window.addEventListener('mouseup', _upHandler);
});
return el;
};
var heartbeat_attempts = 0;
var heartbeat_internal = setInterval(async function () {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
if (!SESSION_ID) return;
let _session = SESSION_OBJ[SESSION_ID];
if (!_session) return;
if (_session.crawler) return;
const set_idle = async function (stat) {
var datasource_changes = {
[0]: {
['data_system']: { SYS_GLOBAL_BOL_IDLE: stat },
},
};
await func.datasource.update(SESSION_ID, datasource_changes);
};
if (Date.now() - CLIENT_ACTIVITY_TS > 30000) {
set_idle(1);
} else {
set_idle(0);
}
if (_session.engine_mode === 'live_preview' || !_session.opt.enable_user_assist) {
return;
}
const ret = await func.common
.db(SESSION_ID, 'heartbeat', {
stat: Date.now() - CLIENT_ACTIVITY_TS > 30000 ? 1 : 2,
})
.catch((err) => {
heartbeat_attempts++;
console.warn(err.message);
if (heartbeat_attempts < 10) return;
clearInterval(heartbeat_internal);
});
heartbeat_attempts = 0;
if (ret?.session_stat === 3) {
await func.index.delete_pouch();
// window.location.href = `https://${_session.domain}/error?error_code=408`;
platform.reload_top_window(); //href = `https://${_session.domain}?ts=${Date.now()}`;
}
document.body.classList.remove('get_support_request');
const _reqTitle = document.body.querySelector('.get_support_request_title');
if (_reqTitle) _reqTitle.remove();
document.body.classList.remove('get_support_online');
const _onlineTitle = document.body.querySelector('.get_support_online_title');
if (_onlineTitle) _onlineTitle.remove();
if (ret?.data?.peer?.peer_status === 1) {
if (ret.data.peer.peer_regional_server) {
get_support.init_peer(SESSION_ID, ret.data.peer.peer_regional_server);
}
document.body.classList.add('get_support_request');
const oldReqTitle = document.body.querySelector('.get_support_request_title');
if (oldReqTitle) oldReqTitle.remove();
const reqDiv = document.createElement('div');
reqDiv.className = 'get_support_request_title';
reqDiv.textContent = 'Support pending';
document.body.appendChild(reqDiv);
}
if (ret?.data?.peer?.peer_status === 2) {
document.body.classList.add('get_support_online');
const oldOnlineTitle = document.body.querySelector('.get_support_online_title');
if (oldOnlineTitle) oldOnlineTitle.remove();
const onlineDiv = document.createElement('div');
onlineDiv.className = 'get_support_online_title';
onlineDiv.textContent = 'Support online';
document.body.appendChild(onlineDiv);
}
_session.res_token = ret.res_token;
}, 30000);
glb.WINDOW_LOCATION_SEARCH = platform.get_url_search();
};
func.index.checkConnectivity = async function () {
const platform = func.runtime.platform;
const endpoints = ['https://xuda.ai/favicon.ico']; //'https://www.cloudflare.com/favicon.ico',
const promises = endpoints.map((url) =>
fetch(url, { method: 'HEAD', mode: 'no-cors' })
.then(() => true)
.catch(() => false),
);
const results = await Promise.all(promises);
IS_ONLINE = results.some((result) => result === true);
// Add event listeners for online/offline events
platform.add_window_listener('online', function () {
IS_ONLINE = true;
document.body.dispatchEvent(new Event('set_db_replication_from_server'));
});
platform.add_window_listener('offline', function () {
IS_ONLINE = false;
});
};
func.index.init_service_workers = function () {
const platform = func.runtime.platform;
if (platform.has_service_worker()) {
platform.add_service_worker_listener('message', function (event) {
console.log('serviceWorker message:', event);
});
platform.register_service_worker('xuda-sw.js').then(
function (registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
glb.sw_registration = registration;
},
function (err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
},
);
}
};
func.index.delete_pouch = async function (SESSION_ID = Object.keys(SESSION_OBJ)[0]) {
const db = await func.utils.connect_pouchdb(SESSION_ID);
try {
return await db.destroy();
} catch (err) {
console.log(err);
}
};
func.index.new_webworker = async function (SESSION_ID, prog_obj, obj) {
const worker_registry = func.runtime.workers.ensure_registry(SESSION_ID);
var worker_id = Object.keys(worker_registry).length + 1;
var _session = SESSION_OBJ[SESSION_ID];
if (!_session.engine_mode === 'docker') {
ver = APP_OBJ[_session.app_id].app_version;
}
var build_id = ['live_preview', 'miniapp'].includes(_session.engine_mode) ? '' : _session.opt.app_build_id;
if (typeof XUDA_BUILD_ID !== 'undefined') {
build_id = XUDA_BUILD_ID;
}
const worker_name = func.runtime.workers.build_worker_name(glb.worker_type, _session, prog_obj, worker_id, build_id);
const init_worker_session = function (worker_id) {
const { root_element, ...sessionData } = SESSION_OBJ[SESSION_ID];
var _session = JSON.parse(JSON.stringify(sessionData));
const get_parent_ds = function (ds) {
var ds_obj = {};
if (ds && _session.DS_GLB[ds].parentDataSourceNo !== null) {
ds_obj[ds] = func.utils.clean_returned_datasource(SESSION_ID, ds);
for (const [key, val] of Object.entries(get_parent_ds(_session.DS_GLB[ds].parentDataSourceNo))) {
ds_obj[key] = val;
}
} else {
ds_obj[ds] = func.utils.clean_returned_datasource(SESSION_ID, ds);
}
return ds_obj;
};
var ds_obj = {};
// send existing datasources to the worker when initiating on demand workers
if (typeof obj?.data?.parentDataSourceNoP !== 'undefined') {
ds_obj = get_parent_ds(obj.data.parentDataSourceNoP);
}
_session.DS_GLB = {};
_session.WORKER_OBJ = {
jobs: [],
num: 1000,
stat: null,
};
var app_id = _session.app_id;
var data = {
SESSION_ID,
app_id,
APP_OBJ: APP_OBJ[app_id],
PROJECT_OBJ: PROJECT_OBJ[app_id],
DOCS_OBJ: DOCS_OBJ[app_id],
APP_INFO: glb.APP_INFO[app_id],
DEBUG_MODE: glb.DEBUG_MODE,
DEBUG_INFO_OBJ: glb.DEBUG_INFO_OBJ,
WINDOW_LOCATION_SEARCH: glb.WINDOW_LOCATION_SEARCH,
ROOT_ELEMENT_ATTRIBUTES: glb.ROOT_ELEMENT_ATTRIBUTES,
DS_GLB: ds_obj,
SESSION_INFO: JSON.parse(JSON.stringify(_session)),
session_id: SESSION_ID,
engine_mode: _session.engine_mode,
STUDIO_WEBSOCKET_CONNECTION_ID: STUDIO_WEBSOCKET_CONNECTION_ID,
};
if (_session.engine_mode === 'live_preview') {
data.DOCS_OBJ = DOCS_OBJ[app_id];
}
delete data.SESSION_INFO.root_element;
data.SESSION_INFO.worker_id = worker_id;
var ds_arr = Object.keys(_session.DS_GLB);
worker_registry[worker_id].ds_arr = ds_arr;
delete data.SESSION_INFO.DS_UI_EVENTS_GLB; // contains function produce error
func.runtime.workers.send_message(
SESSION_ID,
worker_id,
_session,
{
service: 'init',
data: func.runtime.workers.is_server_transport(_session) ? data : JSON.stringify(data),
worker_id: worker_id,
},
WEBSOCKET_PROCESS_PID,
);
};
const create_worker = async function () {
return new Promise((resolve, reject) => {
if (glb.worker_type === 'Dev') {
func.runtime.workers.set_registry_entry(SESSION_ID, worker_id, {
worker: new Worker('js/xuda_worker.js', {
name: worker_name,
}),
promise_queue: {},
});
} else {
function getWorkerURL(url) {
const content = `importScripts( "${url}" );`;
return URL.createObjectURL(new Blob([content], { type: 'text/javascript' }));
}
const _session = SESSION_OBJ[SESSION_ID];
let blob = getWorkerURL(func.common.get_url(SESSION_ID, 'dist', func.utils.get_resource_filename(['live_preview', 'miniapp'].includes(_session.engine_mode) ? '' : _session?.opt?.app_build_id, 'runtime/js/xuda_worker.js')));
func.runtime.workers.set_registry_entry(SESSION_ID, worker_id, {
worker: new Worker(blob, {
name: worker_name,
}),
promise_queue: {},
});
}
func.runtime.workers.get_registry_entry(SESSION_ID, worker_id).worker.addEventListener(
'message',
function (e) {
if (e.data.service === 'worker_ready') {
return resolve();
}
worker_functions(e);
},
false,
);
});
};
const create_websocket = async function () {
return new Promise((resolve, reject) => {
func.runtime.workers.set_registry_entry(SESSION_ID, worker_id, {
worker: RUNTIME_SERVER_WEBSOCKET,
promise_queue: {},
});
func.runtime.workers.get_registry_entry(SESSION_ID, worker_id).worker.on('message', async (e) => {
if (['deployment_server', 'http_call'].includes(e.source)) return;
worker_functions({ data: e });
});
resolve();
});
};
const worker_functions = async function (e_raw) {
return new Promise(async (resolve, reject) => {
var e = {};
e.data = e_raw.data;
if (RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED && (!_session.opt.app_computing_mode || _session.opt.app_computing_mode === 'server')) {
WEBSOCKET_PROCESS_PID = e.data.process_pid;
}
var val = e.data.params;
var fx = {
init_done: async function () {
if (!obj) {
return _resolve();
}
},
alert: function () {
if (val[1] === 'save_on') {
func.UI.utils.save(SESSION_ID, true);
return true;
}
if (val[1] === 'save_off') {
func.UI.utils.save(SESSION_ID, false);
return true;
}
func.utils.alerts.execute(SESSION_ID, val[1], val[2], val[3], val[4]);
return _resolve();
},
progress_on: function () {
func.UI.utils.progressScreen.show(SESSION_ID, val, null);
return _resolve();
},
progress_off: function () {
func.UI.utils.progressScreen.hide();
return _resolve();
},
worker_busy_on: function () {
func.UI.utils.indicator.worker.busy();
return _resolve();
},
worker_busy_off: function () {
func.UI.utils.indicator.worker.normal();
return _resolve();
},
screen_blocker_on: function () {
func.UI.utils.screen_blocker(true, 'Worker');
return _resolve();
},
ajax_error: function () {
// location.reload();
func.utils.request_error(SESSION_ID, 'ajax', 'Session error');
return _resolve();
},
screen_blocker_off: function () {
func.UI.utils.screen_blocker(false, 'Worker');
return _resolve();
},
job: function () {
// fx.datasource_event_changes();
func.events.add_to_queue(
SESSION_ID,
val.typeP,
val.eventIdP,
val.triggerP,
val.functionP,
val.refIdP,
val.containerP,
val.elementP,
val.rowP,
null,
val.descP,
null,
null,
val.dsSessionP,
null,
null,
val.event_propertiesP,
val.calling_triggerP,
val.paramsP,
null,
null,
val.calling_trigger_prop,
val.argumentsP,
val.source_event_idP,
val.calling_job,
val.args,
null,
null,
val.event_optionsP,
);
return _resolve();
},
update_client_eventChangesResults_from_worker: async function () {
await func.datasource.update(SESSION_ID, val, true);
if (!val.worker_id) {
return;
}
fx.acknowledged_worker_with_eventChangesResults_done(val.dssession, val.worker_id);
for await (const [worker_id, ds_arr] of Object.entries(WEB_WORKER[SESSION_ID])) {
if (val.worker_id == worker_id) continue;
await func.index.call_worker(
SESSION_ID,
{
service: 'update_datasource_changes_from_client',
data: data,
},
{
worker_id: worker_id,
},
);
}
},
post_datasource: async function () {
if (typeof val.dsSessionP === 'undefined' || val.dsSessionP === null) return;
if (!SESSION_OBJ[SESSION_ID].DS_GLB[val.dsSessionP]) {
SESSION_OBJ[SESSION_ID].DS_GLB[val.dsSessionP] = val.ds_obj;
} else {
const update_existing_ds_object = function (old_obj, new_obj) {
for (const [key, val] of Object.entries(new_obj)) {
if (typeof val !== 'object') {
old_obj[key] = val2;
} else {
if (!old_obj[key]) {
old_obj[key] = val;
} else update_existing_ds_object(old_obj[key], val);
}
}
};
update_existing_ds_object(SESSION_OBJ[SESSION_ID].DS_GLB[val.dsSessionP], val.ds_obj);
}
var verify_set_parent_ds = function () {
var dsSession = val.dsSessionP;
for (const [key, val] of Object.entries(SESSION_OBJ[SESSION_ID].DS_GLB[dsSession].parent_ds_chain)) {
if (SESSION_OBJ[SESSION_ID].DS_GLB[val]) {
SESSION_OBJ[SESSION_ID].DS_GLB[dsSession].parentDataSourceNo = val;
break;
}
}
};
verify_set_parent_ds();
if (val.ds_obj.dataSourceSessionGlobal > SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal) SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal = val.ds_obj.dataSourceSessionGlobal;
},
return_to_data_source: function (type, dsSessionP, ds) {
var data = {
session_id: SESSION_ID,
dssession: dsSessionP,
onscreen_events_active: ds.v.onscreen_events_active,
viewEventExec_arr: JSON.stringify(ds.viewEventExec_arr),
return_to_data_source_type: type,
};
func.index.call_worker(SESSION_ID, {
service: 'return_to_data_source',
data: data,
});
},
acknowledged_worker_with_eventChangesResults_done: async function (dsSessionP, worker_id) {
var data = {
session_id: SESSION_ID,
dssession: dsSessionP,
};
await func.index.call_worker(
SESSION_ID,
{
service: 'acknowledged_worker_with_eventChangesResults_done',
data: data,
},
{
worker_id: worker_id,
},
);
return _resolve();
},
execute_onscreen_view_events: async function () {
await fx.post_datasource();
var ds = SESSION_OBJ[SESSION_ID].DS_GLB[val.dsSessionP]; //val.ds_obj;
await func.datasource.execute_onscreen_view_events(SESSION_ID, val.dsSessionP, 'onscreen events');
if (!ds.v.onscreen_events_active) return;
var type = ds.v.onscreen_events_active.type;
ds.v.onscreen_events_active = null;
fx.return_to_data_source(type, val.dsSessionP, ds);
return _resolve();
},
execute_local_db_query: async function () {
await fx.post_datasource();
const data = await func.db.get_query(SESSION_ID, val.fileIdP, val.queryP, val.dsSessionP, val.viewSourceDescP, val.sourceP, val.reduceP, val.skipP, val.limitP, val.countP, val.idsP);
var msg = {};
msg.worker_id = worker_id;
msg.data = {};
msg.service = 'return_from_db_query';
msg.data.worker_id = worker_id;
msg.data.callback_id = val.callback_id;
msg.data.session_id = SESSION_ID;
msg.data.data = data;
func.runtime.workers.send_message(SESSION_ID, worker_id, _session, msg, WEBSOCKET_PROCESS_PID);
_resolve();
},
execute_local_sava_data: async function () {
await fx.post_datasource();
const data = await func.db.save_data(SESSION_ID, val.dsSessionP, val.keyP);
var msg = {};
msg.worker_id = worker_id;
msg.data = {};
msg.service = 'return_from_sava_data';
msg.data.worker_id = worker_id;
msg.data.callback_id = val.callback_id;
msg.data.session_id = SESSION_ID;
msg.data.data = data;
func.runtime.workers.send_message(SESSION_ID, worker_id, _session, msg, WEBSOCKET_PROCESS_PID);
_resolve();
},
write_debug_log: function (params) {
func.utils.debug.write(SESSION_ID, val.data);
_resolve();
},
write_log: async function (params) {
if (SESSION_OBJ[SESSION_ID].crawler) return;
const log_payload =
typeof val.data === 'object' && val.data !== null
? val.data
: {
msg: val.data,
source: 'runtime',
log_type: 'error',
};
await func.common.db(SESSION_ID, 'write_log', {
...log_payload,
});
_resolve();
},
change_loaded_image: function (params) {
const new_image = e.data.params.data;
var imgs = document.querySelectorAll('img');
imgs.forEach(function (img) {
var src = img.src;
if (src.indexOf(new_image) > 0) {
img.setAttribute('src', src + '?ts=' + new Date().valueOf());
}
});
_resolve();
},
send_watch_to_studio_websocket: function (params) {
STUDIO_WEBSOCKET.emit('message', val);
_resolve();
},
get_doc_from_studio: async function (params) {
const module = await func.common.get_module(SESSION_ID, `xuda-progs-loader-module.mjs`);
const data = await module.get_doc_from_studio(SESSION_ID, SESSION_OBJ[SESSION_ID].app_id, params.params.doc_id);
func.index.call_worker(SESSION_ID, {
service: 'return_doc_from_studio',
data,
});
_resolve();
},
get_doc_from_websocket: async function (params) {
const module = await func.common.get_module(SESSION_ID, `xuda-progs-loader-module.mjs`);
const data = await module.get_doc_from_websocket(SESSION_ID, SESSION_OBJ[SESSION_ID].app_id, params.params.doc_id);
func.index.call_worker(SESSION_ID, {
service: 'return_doc_from_websocket',
data,
});
_resolve();
},
get_dbs_data_from_websocket: async function (params) {
const data = await func.common.get_data_from_websocket(SESSION_ID, params.params.service, params.params.data);
func.index.call_worker(SESSION_ID, {
service: 'return_dbs_data_from_websocket',
data: {
data,
websocket_queue_num: params.params.websocket_queue_num,
},
});
_resolve();
},
perform_rpi_request_from_studio: async function (params) {
const data = await func.common.db(SESSION_ID, params.params.service, params.params.data);
func.index.call_worker(SESSION_ID, {
service: 'return_rpi_request_from_studio',
data: { data: data.data, table_id: params.params.req_id },
});
_resolve();
},
refresh_document_changes_for_realtime_update: async function (params) {
await func.runtime.ui.refresh_document_changes_for_realtime_update(SESSION_ID, params.params.doc_change);
_resolve();
},
};
const get_promise_queue = function (t, worker_id) {
if (!func.runtime.workers.get_registry_entry(SESSION_ID, worker_id)) {
func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_WRK_030',
source: 'func.index.call_worker',
message: 'worker job not found',
type: 'W',
});
return;
}
return func.runtime.workers.get_promise(SESSION_ID, worker_id, t);
};
const promise_ret = await get_promise_queue(e.data.promise_queue_id, e.data.worker_id);
const _resolve = function (params) {
if (!promise_ret) return resolve();
promise_ret.resolve(params);
setTimeout(function () {
func.runtime.workers.delete_promise(SESSION_ID, e.data.worker_id, e.data.promise_queue_id);
}, 1000);
};
if (fx[e.data.fx_to_execute]) {
return await fx[e.data.fx_to_execute](e.data);
}
if (!promise_ret) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_GEN_000',
source: 'func.index.call_worker',
message: 'promise not found',
type: 'E',
details: {
fx_to_execute: e.data.fx_to_execute,
worker_id: e.data.worker_id,
promise_queue_id: e.data.promise_queue_id,
},
});
return;
}
if (!e.data.fx_to_execute) {
// return datasource
return _resolve(e.data.params);
}
if (e.data.fx_to_execute === 'worker_response') {
return _resolve(e.data.params);
}
});
};
if (RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED && (!_session.opt.app_computing_mode || _session.opt.app_computing_mode === 'server')) {
await create_websocket();
} else {
await create_worker();
}
init_worker_session(worker_id);
return worker_id;
};
func.index.set_ds_0_proxy = function (SESSION_ID) {
const _session = SESSION_OBJ[SESSION_ID];
let _ds = _session.DS_GLB[0];
// const _ds = func.utils.clean_returned_datasource(SESSION_ID, dsSessionP);
function createWatchedObject(obj, onChange) {
const watchers = new WeakMap();
function createProxy(target, path = []) {
if (watchers.has(target)) {
return watchers.get(target);
}
const proxy = new Proxy(target, {
set(obj, prop, value) {
const oldValue = obj[prop];
let currentPath = [...path, prop];
// Set the new value
obj[prop] = value;
// If the new value is an object, make it observable too
if (typeof value === 'object' && value !== null) {
obj[prop] = createProxy(value, currentPath);
}
// Notify of change
// if (oldValue !== value) {
if (!xu_isEqual(value, oldValue)) {
currentPath.shift();
onChange({
path: currentPath.join('.'),
oldValue,
newValue: value,
type: 'set',
timestamp: Date.now(),
});
}
return true;
},
deleteProperty(obj, prop) {
const oldValue = obj[prop];
const currentPath = [...path, prop];
delete obj[prop];
onChange({
path: currentPath.join('.'),
oldValue,
newValue: undefined,
type: 'delete',
timestamp: Date.now(),
});
return true;
},
});
// Make nested objects observable
for (const [key, value] of Object.entries(target)) {
if (typeof value === 'object' && value !== null) {
target[key] = createProxy(value, [...path, key]);
}
}
watchers.set(target, proxy);
return proxy;
}
return createProxy(obj);
}
const watchedDs = createWatchedObject({ _ref: _ds }, async (change) => {
const { path, newValue, oldValue } = change;
// console.log('Change detected:', path);
try {
const _session = SESSION_OBJ[SESSION_ID];
let watch_path = 'data_system.' + path;
const runHandler = () => {
const { handler, once } = _session?.watchers?.[watch_path] || {};
if (handler) handler(change);
if (once) {
delete _session.watchers[watch_path];
}
};
if (_session?.watchers?.[watch_path]) {
runHandler();
} else {
watch_path = 'data_system.SYS_GLOBAL_OBJ_REFS.' + path;
runHandler();
}
if (xu_isEqual(newValue, oldValue)) return;
////////////////////////
const _refs = SESSION_OBJ[SESSION_ID].DS_GLB[0].data_system.SYS_GLOBAL_OBJ_REFS;
for (const [ref_id, val] of Object.entries(_refs)) {
const prefix = `${ref_id}.ds.`;
if (!path.includes(prefix)) continue;
const clean_path_prop = path.split(prefix)[1]; //[path.split('.')[0] === 'SYS_GLOBAL_OBJ_REFS' ? 2 : 1];
if (!clean_path_prop.includes('progDataSource')) continue;
const _ref = SESSION_OBJ[SESSION_ID].DS_GLB[0].data_system.SYS_GLOBAL_OBJ_REFS[ref_id];
if (!_ref) continue;
const _ds = _ref.ds;
const target_ds = _session.DS_GLB[_ds.dsSession];
const target_value = xu_get(target_ds, clean_path_prop);
if (xu_isEqual(target_value, newValue)) continue;
const datasource_changes = {
[_ds.dsSession]: {
['datasource_main']: {
watcher: { path: clean_path_prop, newValue },
},
},
};
await func.datasource.update(SESSION_ID, datasource_changes);
}
return;
///////////////////////
// if (path.split('.')[0] !== 'SYS_GLOBAL_OBJ_REFS') return; //SYS_GLOBAL_OBJ_REFS indicates manual update
// const ref_id = path.split('.')[1];
// if (!ref_id) return;
// const prefix = `SYS_GLOBAL_OBJ_REFS.${ref_id}.ds.`;
// if (!path.includes(prefix)) return;
// const _ref = SESSION_OBJ[SESSION_ID].DS_GLB[0].data_system.SYS_GLOBAL_OBJ_REFS[ref_id];
// if (!_ref) return;
// const _ds = _ref.ds;
// const clean_path_prop = path.replace(prefix, '');
// const target_ds = _session.DS_GLB[_ds.dsSession];
// const target_value = _.get(target_ds, clean_path_prop);
// if (target_value === newValue) return;
// const datasource_changes = {
// [_ds.dsSession]: {
// ['datasource_main']: {
// watcher: { path: path.replace(prefix_path + '.', ''), newValue },
// },
// },
// };
// await func.datasource.update(SESSION_ID, datasource_changes);
///////////////////
// return;
// if (!change.path.includes('data_system.SYS_GLOBAL_OBJ_REFS')) return;
// const ref_id = change.path.split('SYS_GLOBAL_OBJ_REFS.')[1].split('.')[0];
// if (!ref_id) return;
// const _ref = SESSION_OBJ[SESSION_ID].DS_GLB[0].data_system.SYS_GLOBAL_OBJ_REFS[ref_id];
// if (!_ref) return;
// const prefix_path = `data_system.SYS_GLOBAL_OBJ_REFS.${ref_id}.ds`;
// if (!change.path.includes(prefix_path)) return;
// const _ds = _ref.ds;
// const datasource_changes = {
// [_ds.dsSession]: {
// ['datasource_main']: {
// watcher: { path: path.replace(prefix_path + '.', ''), newValue },
// },
// },
// };
// await func.datasource.update(SESSION_ID, datasource_changes);
} catch (error) {}
});
_session.DS_GLB[0] = watchedDs._ref;
};
glb.SLIM_BUNDLE=1;